Bitcoin¶
Import thư viện¶
%pip install arch seaborn
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from arch import arch_model
from arch.univariate import GARCH, ConstantMean
import warnings
warnings.filterwarnings('ignore')
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.stats.diagnostic import acorr_ljungbox
from scipy import stats
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
import seaborn as sns
# Thêm import cho kiểm tra tính dừng
from statsmodels.tsa.stattools import adfuller
Requirement already satisfied: arch in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (7.2.0) Requirement already satisfied: seaborn in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (0.13.2) Requirement already satisfied: numpy>=1.22.3 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.1.3) Requirement already satisfied: scipy>=1.8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (1.15.3) Requirement already satisfied: pandas>=1.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.2.3) Requirement already satisfied: statsmodels>=0.12 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (0.14.4) Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from seaborn) (3.10.3) Requirement already satisfied: contourpy>=1.0.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.2) Requirement already satisfied: cycler>=0.10 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.58.1) Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.8) Requirement already satisfied: packaging>=20.0 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (25.0) Requirement already satisfied: pillow>=8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (11.2.1) Requirement already satisfied: pyparsing>=2.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.3) Requirement already satisfied: python-dateutil>=2.7 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0) Requirement already satisfied: pytz>=2020.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2) Requirement already satisfied: tzdata>=2022.7 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2) Requirement already satisfied: patsy>=0.5.6 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from statsmodels>=0.12->arch) (1.0.1) Requirement already satisfied: six>=1.5 in c:\users\hii\appdata\roaming\python\python311\site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0) Note: you may need to restart the kernel to use updated packages.
[notice] A new release of pip is available: 24.0 -> 25.1.1 [notice] To update, run: python.exe -m pip install --upgrade pip
Bitcoin Dataset¶
Import csv¶
# Đọc file Bitcoin
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\Bitcoin Historical Data.csv"
data = pd.read_csv(file_path)
# Loại bỏ dấu phẩy và chuyển đổi thành float
for col in ['Price', 'Open']:
data[col] = data[col].str.replace(',', '', regex=False).astype(float)
# Xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B'
def convert_volume(val):
val = str(val).replace(',', '').strip()
if 'K' in val:
return float(val.replace('K', '')) * 1_000
elif 'M' in val:
return float(val.replace('M', '')) * 1_000_000
elif 'B' in val:
return float(val.replace('B', '')) * 1_000_000_000
else:
return float(val)
data['Vol.'] = data['Vol.'].apply(convert_volume)
# Đổi Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)
# Select 3 columns: Price, Open, Vol
data_features = data[['Price', 'Open', 'Vol.']].copy()
print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Data shape: (3370, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']
First 5 rows:
Price Open Vol.
Date
2016-03-10 415.8 412.8 55740.0
2016-03-11 419.1 415.8 60630.0
2016-03-12 410.4 419.1 59640.0
2016-03-13 412.4 410.4 34980.0
2016-03-14 414.3 412.4 49330.0
Tổng số dữ liệu: 3370 dòng
Chia 7:3¶
Chuẩn bị dữ liệu cho GARCH¶
# Tính log returns cho GARCH model
data['Log_Return'] = np.log(data['Price'] / data['Price'].shift(1))
data['Simple_Return'] = data['Price'].pct_change()
# Loại bỏ giá trị NaN
data_clean = data.dropna()
print(f"Dữ liệu sau khi tính returns: {len(data_clean)} dòng")
print("\nThống kê mô tả của Log Returns:")
print(data_clean['Log_Return'].describe())
# Kiểm tra tính dừng của chuỗi returns
from statsmodels.tsa.stattools import adfuller
def check_stationarity(series, name):
result = adfuller(series.dropna())
print(f'\n{name} - Kiểm định ADF:')
print(f'ADF Statistic: {result[0]:.6f}')
print(f'p-value: {result[1]:.6f}')
print(f'Critical Values:')
for key, value in result[4].items():
print(f'\t{key}: {value:.3f}')
if result[1] <= 0.05:
print("✓ Chuỗi dừng (stationary)")
else:
print("✗ Chuỗi không dừng (non-stationary)")
return result[1] <= 0.05
# Kiểm tra tính dừng
is_stationary = check_stationarity(data_clean['Log_Return'], 'Log Returns')
Dữ liệu sau khi tính returns: 3369 dòng Thống kê mô tả của Log Returns: count 3369.000000 mean 0.001638 std 0.036633 min -0.497278 25% -0.012538 50% 0.001340 75% 0.017010 max 0.227602 Name: Log_Return, dtype: float64 Log Returns - Kiểm định ADF: ADF Statistic: -40.329533 p-value: 0.000000 Critical Values: 1%: -3.432 5%: -2.862 10%: -2.567 ✓ Chuỗi dừng (stationary)
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data_clean) * 0.7)
train_data = data_clean.iloc[0:train_size].copy()
test_data = data_clean.iloc[train_size:].copy()
# Lấy returns cho train và test
train_returns = train_data['Log_Return'].values
test_returns = test_data['Log_Return'].values
print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
print(f"Train returns shape: {train_returns.shape}")
print(f"Test returns shape: {test_returns.shape}")
Kích thước tập train: 2358 Kích thước tập test: 1011 Train returns shape: (2358,) Test returns shape: (1011,)
Xây dựng mô hình GARCH¶
"""
Xây dựng mô hình GARCH(p,q)
Args:
returns_data: Chuỗi returns
p: Order của GARCH term
q: Order của ARCH term
Returns:
Fitted GARCH model
"""
def build_garch_model(returns_data, p=1, q=1):
# Nhân returns với 100 để có scale phù hợp
returns_scaled = returns_data * 100
# Tạo mô hình GARCH
model = arch_model(returns_scaled, vol='Garch', p=p, q=q, dist='normal')
# Fit mô hình
fitted_model = model.fit(disp='off')
return fitted_model, model
def evaluate_garch_model(fitted_model):
"""
Đánh giá mô hình GARCH
"""
print("="*50)
print("THÔNG TIN MÔ HÌNH GARCH")
print("="*50)
print(fitted_model.summary())
# Kiểm tra phần dư
residuals = fitted_model.resid
standardized_residuals = residuals / fitted_model.conditional_volatility
# Ljung-Box test cho phần dư
lb_result = acorr_ljungbox(residuals, lags=10, return_df=True)
print(f"\nLjung-Box test p-value (residuals): {lb_result['lb_pvalue'].iloc[-1]:.4f}")
# Ljung-Box test cho phần dư bình phương
lb_result_sq = acorr_ljungbox(residuals**2, lags=10, return_df=True)
print(f"Ljung-Box test p-value (squared residuals): {lb_result_sq['lb_pvalue'].iloc[-1]:.4f}")
return fitted_model.aic, fitted_model.bic
def forecast_garch_prices(fitted_model, initial_price, forecast_horizon):
"""
Dự đoán giá sử dụng GARCH model
Args:
fitted_model: Mô hình GARCH đã fit
initial_price: Giá ban đầu
forecast_horizon: Số ngày dự đoán
Returns:
Dự đoán giá và volatility
"""
# Forecast returns và volatility
forecast = fitted_model.forecast(horizon=forecast_horizon)
# Lấy mean và variance dự đoán
forecast_mean = forecast.mean.iloc[-1].values / 100 # Scale về decimal
forecast_variance = forecast.variance.iloc[-1].values / 10000 # Scale về decimal
# Tạo random walks cho price prediction
np.random.seed(42) # Để kết quả reproducible
predicted_prices = []
current_price = initial_price
for i in range(forecast_horizon):
# Sử dụng mean return và add random noise based on predicted volatility
return_pred = forecast_mean[i] + np.random.normal(0, np.sqrt(forecast_variance[i]))
current_price = current_price * np.exp(return_pred)
predicted_prices.append(current_price)
return np.array(predicted_prices), np.sqrt(forecast_variance) * 100
def plot_garch_diagnostics(fitted_model, returns_data, train_data_index):
"""
Vẽ biểu đồ chẩn đoán cho mô hình GARCH
"""
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# 1. Returns và Conditional Volatility
# Sử dụng index từ train_data thay vì fitted_model.resid.index
dates = train_data_index
axes[0, 0].plot(dates, returns_data * 100, alpha=0.7, label='Returns (%)')
axes[0, 0].plot(dates, fitted_model.conditional_volatility, color='red', label='Conditional Volatility')
axes[0, 0].set_title('Returns và Conditional Volatility')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
axes[0, 0].tick_params(axis='x', rotation=45)
# 2. Standardized Residuals
std_resid = fitted_model.resid / fitted_model.conditional_volatility
axes[0, 1].plot(dates, std_resid)
axes[0, 1].set_title('Standardized Residuals')
axes[0, 1].axhline(y=0, color='red', linestyle='--', alpha=0.7)
axes[0, 1].grid(True, alpha=0.3)
axes[0, 1].tick_params(axis='x', rotation=45)
# 3. Q-Q Plot
stats.probplot(std_resid, dist="norm", plot=axes[1, 0])
axes[1, 0].set_title('Q-Q Plot of Standardized Residuals')
axes[1, 0].grid(True, alpha=0.3)
# 4. ACF of Squared Residuals
from statsmodels.tsa.stattools import acf
squared_resid = std_resid ** 2
lags = 20
acf_vals = acf(squared_resid, nlags=lags)
axes[1, 1].bar(range(lags+1), acf_vals, alpha=0.7)
axes[1, 1].set_title('ACF of Squared Standardized Residuals')
axes[1, 1].set_xlabel('Lag')
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Huấn luyện mô hình GARCH(1,1)¶
# Fit mô hình GARCH(1,1) cho tập train
print("Đang huấn luyện mô hình GARCH(1,1)...")
fitted_garch, garch_model = build_garch_model(train_returns, p=1, q=1)
# Đánh giá mô hình
aic, bic = evaluate_garch_model(fitted_garch)
# Vẽ biểu đồ chẩn đoán với train_returns và train_data index
plot_garch_diagnostics(fitted_garch, train_returns, train_data.index)
Đang huấn luyện mô hình GARCH(1,1)...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -6476.88
Distribution: Normal AIC: 12961.8
Method: Maximum Likelihood BIC: 12984.8
No. Observations: 2358
Date: Tue, Jun 03 2025 Df Residuals: 2357
Time: 20:39:00 Df Model: 1
Mean Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
mu 0.2228 7.634e-02 2.919 3.515e-03 [7.319e-02, 0.372]
Volatility Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
omega 0.7630 0.308 2.474 1.337e-02 [ 0.158, 1.367]
alpha[1] 0.1290 4.979e-02 2.590 9.607e-03 [3.136e-02, 0.227]
beta[1] 0.8398 3.993e-02 21.031 3.437e-98 [ 0.762, 0.918]
==========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0835
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình trên tập test¶
# Dự đoán trên tập test
test_size_days = len(test_data)
last_train_price = train_data['Price'].iloc[-1]
# Forecast cho test period
test_forecast = fitted_garch.forecast(horizon=test_size_days)
forecast_returns = test_forecast.mean.iloc[-1].values / 100
forecast_volatility = np.sqrt(test_forecast.variance.iloc[-1].values) / 100
# Tính predicted prices cho test set
predicted_prices_test = []
current_price = last_train_price
for i in range(test_size_days):
# Sử dụng predicted return
predicted_return = forecast_returns[i]
current_price = current_price * np.exp(predicted_return)
predicted_prices_test.append(current_price)
predicted_prices_test = np.array(predicted_prices_test)
# Tính metrics
actual_test_prices = test_data['Price'].values
mape = mean_absolute_percentage_error(actual_test_prices, predicted_prices_test)
mse = mean_squared_error(actual_test_prices, predicted_prices_test)
rmse = np.sqrt(mse)
print(f'Kết quả đánh giá mô hình GARCH(1,1):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'AIC: {aic:.2f}')
print(f'BIC: {bic:.2f}')
# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data.index, actual_test_prices, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (7:3)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1): MAPE: 0.55% MSE: 1572132497.08 RMSE: 39650.13 AIC: 12961.76 BIC: 12984.82
Dự đoán tương lai¶
# Dự đoán 30, 60, 90 ngày tiếp theo
last_price = data_clean['Price'].iloc[-1]
# Forecast prices
forecast_30, vol_30 = forecast_garch_prices(fitted_garch, last_price, 30)
forecast_60, vol_60 = forecast_garch_prices(fitted_garch, last_price, 60)
forecast_90, vol_90 = forecast_garch_prices(fitted_garch, last_price, 90)
# Tạo dates cho forecasts
forecast_dates_30 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
# Tạo DataFrames
forecast_df_30 = pd.DataFrame(forecast_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecast_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecast_90, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa
plt.figure(figsize=(16, 10))
# Vẽ dữ liệu lịch sử
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán test
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán trên test set', color='orange', linewidth=2, alpha=0.8)
# Vẽ dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
label='Dự đoán 30 ngày', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'],
label='Dự đoán 60 ngày', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'],
label='Dự đoán 90 ngày', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Đường phân cách
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Bitcoin bằng GARCH(1,1) - Split 7:3', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# For the 7:3 split, we're using GARCH(1,1) model
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 7:3):')
print(f'Giá cao nhất: ${forecast_30.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30.min():.2f}')
print(f'Giá trung bình: ${forecast_30.mean():.2f}')
print(f'Volatility trung bình: {vol_30.mean():.2f}%')
Dự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 7:3): Giá cao nhất: $125180.21 Giá thấp nhất: $85620.35 Giá trung bình: $105057.66 Volatility trung bình: 4.07%
Chia 8:2¶
Chuẩn bị dữ liệu 8:2¶
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3369 dòng
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data_clean) * 0.8)
train_data_82 = data_clean.iloc[0:train_size_82].copy()
test_data_82 = data_clean.iloc[train_size_82:].copy()
# Lấy returns cho train và test
train_returns_82 = train_data_82['Log_Return'].values
test_returns_82 = test_data_82['Log_Return'].values
print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2695 Kích thước tập test 8:2: 674
Huấn luyện mô hình GARCH(1,1) cho 8:2¶
# Fit mô hình GARCH(1,1) cho split 8:2
print("Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...")
fitted_garch_82, garch_model_82 = build_garch_model(train_returns_82, p=1, q=1)
# Đánh giá mô hình
aic_82, bic_82 = evaluate_garch_model(fitted_garch_82)
Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -7273.22
Distribution: Normal AIC: 14554.4
Method: Maximum Likelihood BIC: 14578.0
No. Observations: 2695
Date: Tue, Jun 03 2025 Df Residuals: 2694
Time: 20:39:01 Df Model: 1
Mean Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
mu 0.1954 6.449e-02 3.030 2.444e-03 [6.903e-02, 0.322]
Volatility Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
omega 0.7005 0.245 2.856 4.290e-03 [ 0.220, 1.181]
alpha[1] 0.1464 4.761e-02 3.076 2.096e-03 [5.314e-02, 0.240]
beta[1] 0.8262 3.423e-02 24.138 9.952e-129 [ 0.759, 0.893]
==========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0936
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình 8:2¶
# Dự đoán trên tập test 8:2
test_size_days_82 = len(test_data_82)
last_train_price_82 = train_data_82['Price'].iloc[-1]
# Forecast cho test period
test_forecast_82 = fitted_garch_82.forecast(horizon=test_size_days_82)
forecast_returns_82 = test_forecast_82.mean.iloc[-1].values / 100
# Tính predicted prices cho test set 8:2
predicted_prices_test_82 = []
current_price_82 = last_train_price_82
for i in range(test_size_days_82):
predicted_return = forecast_returns_82[i]
current_price_82 = current_price_82 * np.exp(predicted_return)
predicted_prices_test_82.append(current_price_82)
predicted_prices_test_82 = np.array(predicted_prices_test_82)
# Tính metrics cho 8:2
actual_test_prices_82 = test_data_82['Price'].values
mape_82 = mean_absolute_percentage_error(actual_test_prices_82, predicted_prices_test_82)
mse_82 = mean_squared_error(actual_test_prices_82, predicted_prices_test_82)
rmse_82 = np.sqrt(mse_82)
print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'AIC: {aic_82:.2f}')
print(f'BIC: {bic_82:.2f}')
# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data_82.index, actual_test_prices_82, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data_82.index, predicted_prices_test_82, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (8:2)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2: MAPE: 0.13% MSE: 119564962.50 RMSE: 10934.58 AIC: 14554.44 BIC: 14578.03
Dự đoán tương lai 8:2¶
# Dự đoán 30, 60, 90 ngày tiếp theo cho 8:2
forecast_30_82, vol_30_82 = forecast_garch_prices(fitted_garch_82, last_price, 30)
forecast_60_82, vol_60_82 = forecast_garch_prices(fitted_garch_82, last_price, 60)
forecast_90_82, vol_90_82 = forecast_garch_prices(fitted_garch_82, last_price, 90)
# Tạo DataFrames cho 8:2
forecast_df_30_82 = pd.DataFrame(forecast_30_82, index=forecast_dates_30, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecast_60_82, index=forecast_dates_60, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecast_90_82, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa 8:2
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_82.index, predicted_prices_test_82, label='Dự đoán trên test set (8:2)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
label='Dự đoán 30 ngày (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
label='Dự đoán 60 ngày (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
label='Dự đoán 90 ngày (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Bitcoin bằng GARCH(1,1) - Split 8:2', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 8:2):')
print(f'Giá cao nhất: ${forecast_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30_82.min():.2f}')
print(f'Giá trung bình: ${forecast_30_82.mean():.2f}')
print(f'Volatility trung bình: {vol_30_82.mean():.2f}%')
Dự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 8:2): Giá cao nhất: $119449.90 Giá thấp nhất: $86091.68 Giá trung bình: $103118.90 Volatility trung bình: 3.39%
Chia 9:1¶
Chuẩn bị dữ liệu 9:1¶
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3369 dòng
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data_clean) * 0.9)
train_data_91 = data_clean.iloc[0:train_size_91].copy()
test_data_91 = data_clean.iloc[train_size_91:].copy()
# Lấy returns cho train và test
train_returns_91 = train_data_91['Log_Return'].values
test_returns_91 = test_data_91['Log_Return'].values
print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3032 Kích thước tập test 9:1: 337
Huấn luyện mô hình GARCH(1,1) cho 9:1¶
# Fit mô hình GARCH(1,1) cho split 9:1
print("Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...")
fitted_garch_91, garch_model_91 = build_garch_model(train_returns_91, p=1, q=1)
# Đánh giá mô hình
aic_91, bic_91 = evaluate_garch_model(fitted_garch_91)
Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -8062.68
Distribution: Normal AIC: 16133.4
Method: Maximum Likelihood BIC: 16157.4
No. Observations: 3032
Date: Tue, Jun 03 2025 Df Residuals: 3031
Time: 20:39:02 Df Model: 1
Mean Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
mu 0.1932 5.804e-02 3.330 8.694e-04 [7.949e-02, 0.307]
Volatility Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
omega 0.6252 0.208 3.003 2.678e-03 [ 0.217, 1.033]
alpha[1] 0.1420 4.572e-02 3.107 1.891e-03 [5.244e-02, 0.232]
beta[1] 0.8308 3.478e-02 23.885 4.393e-126 [ 0.763, 0.899]
==========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0554
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình 9:1¶
# Dự đoán trên tập test 9:1
test_size_days_91 = len(test_data_91)
last_train_price_91 = train_data_91['Price'].iloc[-1]
# Forecast cho test period
test_forecast_91 = fitted_garch_91.forecast(horizon=test_size_days_91)
forecast_returns_91 = test_forecast_91.mean.iloc[-1].values / 100
# Tính predicted prices cho test set 9:1
predicted_prices_test_91 = []
current_price_91 = last_train_price_91
for i in range(test_size_days_91):
predicted_return = forecast_returns_91[i]
current_price_91 = current_price_91 * np.exp(predicted_return)
predicted_prices_test_91.append(current_price_91)
predicted_prices_test_91 = np.array(predicted_prices_test_91)
# Tính metrics cho 9:1
actual_test_prices_91 = test_data_91['Price'].values
mape_91 = mean_absolute_percentage_error(actual_test_prices_91, predicted_prices_test_91)
mse_91 = mean_squared_error(actual_test_prices_91, predicted_prices_test_91)
rmse_91 = np.sqrt(mse_91)
print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'AIC: {aic_91:.2f}')
print(f'BIC: {bic_91:.2f}')
# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data_91.index, actual_test_prices_91, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data_91.index, predicted_prices_test_91, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (9:1)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1: MAPE: 0.13% MSE: 136029586.18 RMSE: 11663.17 AIC: 16133.35 BIC: 16157.42
Dự đoán tương lai 9:1¶
# Dự đoán 30, 60, 90 ngày tiếp theo cho 9:1
forecast_30_91, vol_30_91 = forecast_garch_prices(fitted_garch_91, last_price, 30)
forecast_60_91, vol_60_91 = forecast_garch_prices(fitted_garch_91, last_price, 60)
forecast_90_91, vol_90_91 = forecast_garch_prices(fitted_garch_91, last_price, 90)
# Tạo DataFrames cho 9:1
forecast_df_30_91 = pd.DataFrame(forecast_30_91, index=forecast_dates_30, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecast_60_91, index=forecast_dates_60, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecast_90_91, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa 9:1
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_91.index, predicted_prices_test_91, label='Dự đoán trên test set (9:1)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
label='Dự đoán 30 ngày (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
label='Dự đoán 60 ngày (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
label='Dự đoán 90 ngày (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Bitcoin bằng GARCH(1,1) - Split 9:1', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f'\nDự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 9:1):')
print(f'Giá cao nhất: ${forecast_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30_91.min():.2f}')
print(f'Giá trung bình: ${forecast_30_91.mean():.2f}')
print(f'Volatility trung bình: {vol_30_91.mean():.2f}%')
Dự đoán giá Bitcoin 30 ngày tiếp theo (GARCH(1,1) - 9:1): Giá cao nhất: $120899.64 Giá thấp nhất: $86736.24 Giá trung bình: $103922.23 Volatility trung bình: 3.49%
So sánh 3 tỉ lệ GARCH(1,1)¶
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu cho GARCH(1,1)
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH(1,1) MODEL")
print("="*80)
# Thu thập thông tin từ 3 splits
garch_splits_info = {
'7:3': {
'train_size': len(train_data),
'test_size': len(test_data),
'mape': mape,
'mse': mse,
'rmse': rmse,
'aic': aic,
'bic': bic,
'avg_volatility': vol_30.mean()
},
'8:2': {
'train_size': len(train_data_82),
'test_size': len(test_data_82),
'mape': mape_82,
'mse': mse_82,
'rmse': rmse_82,
'aic': aic_82,
'bic': bic_82,
'avg_volatility': vol_30_82.mean()
},
'9:1': {
'train_size': len(train_data_91),
'test_size': len(test_data_91),
'mape': mape_91,
'mse': mse_91,
'rmse': rmse_91,
'aic': aic_91,
'bic': bic_91,
'avg_volatility': vol_30_91.mean()
}
}
# In bảng so sánh GARCH
for split, info in garch_splits_info.items():
print(f"\n{split} Split (GARCH(1,1)):")
print(f" Kích thước train: {info['train_size']:,} mẫu")
print(f" Kích thước test: {info['test_size']:,} mẫu")
print(f" MAPE: {info['mape']:.2f}%")
print(f" MSE: {info['mse']:,.2f}")
print(f" RMSE: {info['rmse']:,.2f}")
print(f" AIC: {info['aic']:.2f}")
print(f" BIC: {info['bic']:.2f}")
print(f" Avg Volatility: {info['avg_volatility']:.2f}%")
================================================================================ SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH(1,1) MODEL ================================================================================ 7:3 Split (GARCH(1,1)): Kích thước train: 2,358 mẫu Kích thước test: 1,011 mẫu MAPE: 0.55% MSE: 1,572,132,497.08 RMSE: 39,650.13 AIC: 12961.76 BIC: 12984.82 Avg Volatility: 4.07% 8:2 Split (GARCH(1,1)): Kích thước train: 2,695 mẫu Kích thước test: 674 mẫu MAPE: 0.13% MSE: 119,564,962.50 RMSE: 10,934.58 AIC: 14554.44 BIC: 14578.03 Avg Volatility: 3.39% 9:1 Split (GARCH(1,1)): Kích thước train: 3,032 mẫu Kích thước test: 337 mẫu MAPE: 0.13% MSE: 136,029,586.18 RMSE: 11,663.17 AIC: 16133.35 BIC: 16157.42 Avg Volatility: 3.49%
# Vẽ biểu đồ so sánh các metrics cho GARCH(1,1)
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
# 1. So sánh MAPE
mape_values = [garch_splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%) - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 2. So sánh RMSE
rmse_values = [garch_splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD) - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')
# 3. So sánh AIC
aic_values = [garch_splits_info[split]['aic'] for split in splits]
axes[0, 2].bar(splits, aic_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh AIC - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('AIC')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(aic_values):
axes[0, 2].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')
# 4. So sánh BIC
bic_values = [garch_splits_info[split]['bic'] for split in splits]
axes[1, 0].bar(splits, bic_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh BIC - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('BIC')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(bic_values):
axes[1, 0].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')
# 5. So sánh Average Volatility
vol_values = [garch_splits_info[split]['avg_volatility'] for split in splits]
axes[1, 1].bar(splits, vol_values, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Avg Volatility (%) - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('Volatility (%)')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(vol_values):
axes[1, 1].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 6. So sánh kích thước test set
test_sizes = [garch_splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set - GARCH(1,1)', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')
plt.tight_layout()
plt.show()
# Tạo DataFrame tổng hợp kết quả GARCH
garch_comparison_df = pd.DataFrame({
'Split': ['7:3', '8:2', '9:1'],
'Train_Size': [garch_splits_info[split]['train_size'] for split in ['7:3', '8:2', '9:1']],
'Test_Size': [garch_splits_info[split]['test_size'] for split in ['7:3', '8:2', '9:1']],
'MAPE (%)': [garch_splits_info[split]['mape'] for split in ['7:3', '8:2', '9:1']],
'RMSE (USD)': [garch_splits_info[split]['rmse'] for split in ['7:3', '8:2', '9:1']],
'MSE': [garch_splits_info[split]['mse'] for split in ['7:3', '8:2', '9:1']],
'AIC': [garch_splits_info[split]['aic'] for split in ['7:3', '8:2', '9:1']],
'BIC': [garch_splits_info[split]['bic'] for split in ['7:3', '8:2', '9:1']],
'Avg_Volatility (%)': [garch_splits_info[split]['avg_volatility'] for split in ['7:3', '8:2', '9:1']]
})
print("\nBẢNG TỔNG HỢP KẾT QUẢ GARCH(1,1):")
print("="*100)
print(garch_comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ GARCH(1,1): ==================================================================================================== Split Train_Size Test_Size MAPE (%) RMSE (USD) MSE AIC BIC Avg_Volatility (%) 7:3 2358 1011 0.5514 39650.1261 1572132497.0754 12961.7608 12984.8231 4.0730 8:2 2695 674 0.1299 10934.5765 119564962.5033 14554.4358 14578.0325 3.3878 9:1 3032 337 0.1255 11663.1722 136029586.1774 16133.3505 16157.4184 3.4942
# Phân tích và đưa ra khuyến nghị cho GARCH
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH(1,1) MODEL")
print("="*80)
splits = ['7:3', '8:2', '9:1']
# Tìm split tốt nhất cho từng metric
best_mape_split_garch = splits[np.argmin([garch_splits_info[split]['mape'] for split in splits])]
best_rmse_split_garch = splits[np.argmin([garch_splits_info[split]['rmse'] for split in splits])]
best_aic_split_garch = splits[np.argmin([garch_splits_info[split]['aic'] for split in splits])]
best_bic_split_garch = splits[np.argmin([garch_splits_info[split]['bic'] for split in splits])]
print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH(1,1)):")
print(f" • Tốt nhất theo MAPE: {best_mape_split_garch} ({garch_splits_info[best_mape_split_garch]['mape']:.2f}%)")
print(f" • Tốt nhất theo RMSE: {best_rmse_split_garch} ({garch_splits_info[best_rmse_split_garch]['rmse']:,.2f} USD)")
print(f" • Tốt nhất theo AIC: {best_aic_split_garch} ({garch_splits_info[best_aic_split_garch]['aic']:.2f})")
print(f" • Tốt nhất theo BIC: {best_bic_split_garch} ({garch_splits_info[best_bic_split_garch]['bic']:.2f})")
# Tính điểm tổng hợp cho GARCH
def calculate_garch_rank_score(splits_info):
scores = {}
splits_list = list(splits_info.keys())
# Rank cho các metrics (thấp hơn = tốt hơn)
mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
aic_rank = sorted(splits_list, key=lambda x: splits_info[x]['aic'])
bic_rank = sorted(splits_list, key=lambda x: splits_info[x]['bic'])
for split in splits_list:
# Điểm rank với trọng số cho GARCH
score = (mape_rank.index(split) + 1) * 0.35 + \
(rmse_rank.index(split) + 1) * 0.35 + \
(aic_rank.index(split) + 1) * 0.15 + \
(bic_rank.index(split) + 1) * 0.15
scores[split] = score
return scores
# Tính điểm tổng hợp
garch_scores = calculate_garch_rank_score(garch_splits_info)
best_overall_split_garch = min(garch_scores, key=garch_scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP GARCH(1,1) (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):")
for split in garch_splits_info.keys():
print(f" • {split}: {garch_scores[split]:.2f} điểm")
print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH(1,1):")
print(f" 🏆 MÔ HÌNH GARCH(1,1) TỐT NHẤT: Split {best_overall_split_garch}")
print(f" 📊 Lý do:")
print(f" - MAPE: {garch_splits_info[best_overall_split_garch]['mape']:.2f}%")
print(f" - RMSE: {garch_splits_info[best_overall_split_garch]['rmse']:,.2f} USD")
print(f" - AIC: {garch_splits_info[best_overall_split_garch]['aic']:.2f}")
print(f" - BIC: {garch_splits_info[best_overall_split_garch]['bic']:.2f}")
print(f" - Avg Volatility: {garch_splits_info[best_overall_split_garch]['avg_volatility']:.2f}%")
print(f" - Tập test có {garch_splits_info[best_overall_split_garch]['test_size']:,} mẫu")
print(f"\n4. NHẬN XÉT VỀ GARCH(1,1) MODEL:")
print(" • GARCH(1,1) là mô hình chuẩn được sử dụng rộng rãi")
print(" • Hiệu quả trong việc mô hình hóa volatility clustering")
print(" • Phù hợp với dữ liệu tài chính có tính heteroskedasticity")
print(" • Cung cấp forecast về volatility cùng với price prediction")
print(" • AIC và BIC giúp đánh giá model fit và complexity")
print(" • Đã kiểm tra tính dừng của chuỗi returns trước khi áp dụng")
print(f"\n ✅ ƯU ĐIỂM GARCH(1,1):")
print(" • Đơn giản, dễ hiểu và implement")
print(" • Ít tham số, tránh overfitting")
print(" • Được chấp nhận rộng rãi trong cộng đồng tài chính")
print(" • Phù hợp cho short-term forecasting với dữ liệu có volatility cao như Bitcoin")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH(1,1) MODEL
================================================================================
1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH(1,1)):
• Tốt nhất theo MAPE: 9:1 (0.13%)
• Tốt nhất theo RMSE: 8:2 (10,934.58 USD)
• Tốt nhất theo AIC: 7:3 (12961.76)
• Tốt nhất theo BIC: 7:3 (12984.82)
2. ĐIỂM TỔNG HỢP GARCH(1,1) (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):
• 7:3: 2.40 điểm
• 8:2: 1.65 điểm
• 9:1: 1.95 điểm
3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH(1,1):
🏆 MÔ HÌNH GARCH(1,1) TỐT NHẤT: Split 8:2
📊 Lý do:
- MAPE: 0.13%
- RMSE: 10,934.58 USD
- AIC: 14554.44
- BIC: 14578.03
- Avg Volatility: 3.39%
- Tập test có 674 mẫu
4. NHẬN XÉT VỀ GARCH(1,1) MODEL:
• GARCH(1,1) là mô hình chuẩn được sử dụng rộng rãi
• Hiệu quả trong việc mô hình hóa volatility clustering
• Phù hợp với dữ liệu tài chính có tính heteroskedasticity
• Cung cấp forecast về volatility cùng với price prediction
• AIC và BIC giúp đánh giá model fit và complexity
• Đã kiểm tra tính dừng của chuỗi returns trước khi áp dụng
✅ ƯU ĐIỂM GARCH(1,1):
• Đơn giản, dễ hiểu và implement
• Ít tham số, tránh overfitting
• Được chấp nhận rộng rãi trong cộng đồng tài chính
• Phù hợp cho short-term forecasting với dữ liệu có volatility cao như Bitcoin
ETH¶
Import thư viện¶
%pip install arch seaborn
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from arch import arch_model
from arch.univariate import GARCH, ConstantMean
import warnings
warnings.filterwarnings('ignore')
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.stats.diagnostic import acorr_ljungbox
from scipy import stats
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
import seaborn as sns
# Thêm import cho kiểm tra tính dừng
from statsmodels.tsa.stattools import adfuller
Requirement already satisfied: arch in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (7.2.0) Requirement already satisfied: seaborn in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (0.13.2) Requirement already satisfied: numpy>=1.22.3 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.1.3) Requirement already satisfied: scipy>=1.8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (1.15.3) Requirement already satisfied: pandas>=1.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.2.3) Requirement already satisfied: statsmodels>=0.12 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (0.14.4) Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from seaborn) (3.10.3) Requirement already satisfied: contourpy>=1.0.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.2) Requirement already satisfied: cycler>=0.10 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.58.1) Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.8) Requirement already satisfied: packaging>=20.0 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (25.0) Requirement already satisfied: pillow>=8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (11.2.1) Requirement already satisfied: pyparsing>=2.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.3) Requirement already satisfied: python-dateutil>=2.7 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0) Requirement already satisfied: pytz>=2020.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2) Requirement already satisfied: tzdata>=2022.7 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2) Requirement already satisfied: patsy>=0.5.6 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from statsmodels>=0.12->arch) (1.0.1) Requirement already satisfied: six>=1.5 in c:\users\hii\appdata\roaming\python\python311\site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0) Note: you may need to restart the kernel to use updated packages.
[notice] A new release of pip is available: 24.0 -> 25.1.1 [notice] To update, run: python.exe -m pip install --upgrade pip
ETH Dataset¶
Import csv¶
# Đọc file ETH
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\Ethereum Historical Data.csv"
data = pd.read_csv(file_path)
# Loại bỏ dấu phẩy và chuyển đổi thành float cho Price và Open
for col in ['Price', 'Open']:
data[col] = data[col].str.replace(',', '', regex=False).astype(float)
# Xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B' thành số thực
def convert_volume(val):
val = str(val).replace(',', '').strip()
if 'K' in val:
return float(val.replace('K', '')) * 1_000
elif 'M' in val:
return float(val.replace('M', '')) * 1_000_000
elif 'B' in val:
return float(val.replace('B', '')) * 1_000_000_000
else:
try:
return float(val)
except ValueError:
return np.nan # Trường hợp val là '' hoặc không chuyển được
data['Vol.'] = data['Vol.'].apply(convert_volume)
# Kiểm tra NaN ban đầu trong Vol.
print(f"Trước khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")
# Nội suy giá trị Vol. (chỉ sau khi đã convert sang số)
data['Vol.'] = data['Vol.'].interpolate(method='linear')
# Điền 0 cho NaN còn lại
data['Vol.'] = data['Vol.'].fillna(0)
# Kiểm tra NaN sau xử lý
print(f"Sau khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")
# Đổi Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)
# Select 3 columns: Price, Open, Vol
data_features = data[['Price', 'Open', 'Vol.']].copy()
print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Trước khi xử lý, số NaN ở Vol.: 8
Sau khi xử lý, số NaN ở Vol.: 0
Data shape: (3370, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']
First 5 rows:
Price Open Vol.
Date
2016-03-10 11.75 11.20 0.0
2016-03-11 11.95 11.75 180.0
2016-03-12 12.92 11.95 830.0
2016-03-13 15.07 12.92 1300.0
2016-03-14 12.50 15.07 92180.0
Tổng số dữ liệu: 3370 dòng
Chia 7:3¶
Chuẩn bị dữ liệu cho GARCH¶
# Tính log returns cho GARCH model
data['Log_Return'] = np.log(data['Price'] / data['Price'].shift(1))
data['Simple_Return'] = data['Price'].pct_change()
# Loại bỏ giá trị NaN
data_clean = data.dropna()
print(f"Dữ liệu sau khi tính returns: {len(data_clean)} dòng")
print("\nThống kê mô tả của Log Returns:")
print(data_clean['Log_Return'].describe())
# Kiểm tra tính dừng của chuỗi returns
from statsmodels.tsa.stattools import adfuller
def check_stationarity(series, name):
result = adfuller(series.dropna())
print(f'\n{name} - Kiểm định ADF:')
print(f'ADF Statistic: {result[0]:.6f}')
print(f'p-value: {result[1]:.6f}')
print(f'Critical Values:')
for key, value in result[4].items():
print(f'\t{key}: {value:.3f}')
if result[1] <= 0.05:
print("✓ Chuỗi dừng (stationary)")
else:
print("✗ Chuỗi không dừng (non-stationary)")
return result[1] <= 0.05
# Kiểm tra tính dừng
is_stationary = check_stationarity(data_clean['Log_Return'], 'Log Returns')
Dữ liệu sau khi tính returns: 3369 dòng Thống kê mô tả của Log Returns: count 3369.000000 mean 0.001593 std 0.050663 min -0.589639 25% -0.020598 50% 0.000708 75% 0.023597 max 0.258599 Name: Log_Return, dtype: float64 Log Returns - Kiểm định ADF: ADF Statistic: -32.078477 p-value: 0.000000 Critical Values: 1%: -3.432 5%: -2.862 10%: -2.567 ✓ Chuỗi dừng (stationary)
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data_clean) * 0.7)
train_data = data_clean.iloc[0:train_size].copy()
test_data = data_clean.iloc[train_size:].copy()
# Lấy returns cho train và test
train_returns = train_data['Log_Return'].values
test_returns = test_data['Log_Return'].values
print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
print(f"Train returns shape: {train_returns.shape}")
print(f"Test returns shape: {test_returns.shape}")
Kích thước tập train: 2358 Kích thước tập test: 1011 Train returns shape: (2358,) Test returns shape: (1011,)
Xây dựng mô hình GARCH¶
def build_garch_model(returns_data, p=1, q=1):
"""
Xây dựng mô hình GARCH(p,q)
Args:
returns_data: Chuỗi returns
p: Order của GARCH term
q: Order của ARCH term
Returns:
Fitted GARCH model
"""
# Nhân returns với 100 để có scale phù hợp
returns_scaled = returns_data * 100
# Tạo mô hình GARCH
model = arch_model(returns_scaled, vol='Garch', p=p, q=q, dist='normal')
# Fit mô hình
fitted_model = model.fit(disp='off')
return fitted_model, model
def evaluate_garch_model(fitted_model):
"""
Đánh giá mô hình GARCH
"""
print("="*50)
print("THÔNG TIN MÔ HÌNH GARCH")
print("="*50)
print(fitted_model.summary())
# Kiểm tra phần dư
residuals = fitted_model.resid
standardized_residuals = residuals / fitted_model.conditional_volatility
# Ljung-Box test cho phần dư
lb_result = acorr_ljungbox(residuals, lags=10, return_df=True)
print(f"\nLjung-Box test p-value (residuals): {lb_result['lb_pvalue'].iloc[-1]:.4f}")
# Ljung-Box test cho phần dư bình phương
lb_result_sq = acorr_ljungbox(residuals**2, lags=10, return_df=True)
print(f"Ljung-Box test p-value (squared residuals): {lb_result_sq['lb_pvalue'].iloc[-1]:.4f}")
return fitted_model.aic, fitted_model.bic
def forecast_garch_prices(fitted_model, initial_price, forecast_horizon):
"""
Dự đoán giá sử dụng GARCH model
Args:
fitted_model: Mô hình GARCH đã fit
initial_price: Giá ban đầu
forecast_horizon: Số ngày dự đoán
Returns:
Dự đoán giá và volatility
"""
# Forecast returns và volatility
forecast = fitted_model.forecast(horizon=forecast_horizon)
# Lấy mean và variance dự đoán
forecast_mean = forecast.mean.iloc[-1].values / 100 # Scale về decimal
forecast_variance = forecast.variance.iloc[-1].values / 10000 # Scale về decimal
# Tạo random walks cho price prediction
np.random.seed(42) # Để kết quả reproducible
predicted_prices = []
current_price = initial_price
for i in range(forecast_horizon):
# Sử dụng mean return và add random noise based on predicted volatility
return_pred = forecast_mean[i] + np.random.normal(0, np.sqrt(forecast_variance[i]))
current_price = current_price * np.exp(return_pred)
predicted_prices.append(current_price)
return np.array(predicted_prices), np.sqrt(forecast_variance) * 100
def plot_garch_diagnostics(fitted_model, returns_data, train_data_index):
"""
Vẽ biểu đồ chẩn đoán cho mô hình GARCH
"""
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# 1. Returns và Conditional Volatility
# Sử dụng index từ train_data thay vì fitted_model.resid.index
dates = train_data_index
axes[0, 0].plot(dates, returns_data * 100, alpha=0.7, label='Returns (%)')
axes[0, 0].plot(dates, fitted_model.conditional_volatility, color='red', label='Conditional Volatility')
axes[0, 0].set_title('Returns và Conditional Volatility')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
axes[0, 0].tick_params(axis='x', rotation=45)
# 2. Standardized Residuals
std_resid = fitted_model.resid / fitted_model.conditional_volatility
axes[0, 1].plot(dates, std_resid)
axes[0, 1].set_title('Standardized Residuals')
axes[0, 1].axhline(y=0, color='red', linestyle='--', alpha=0.7)
axes[0, 1].grid(True, alpha=0.3)
axes[0, 1].tick_params(axis='x', rotation=45)
# 3. Q-Q Plot
stats.probplot(std_resid, dist="norm", plot=axes[1, 0])
axes[1, 0].set_title('Q-Q Plot of Standardized Residuals')
axes[1, 0].grid(True, alpha=0.3)
# 4. ACF of Squared Residuals
from statsmodels.tsa.stattools import acf
squared_resid = std_resid ** 2
lags = 20
acf_vals = acf(squared_resid, nlags=lags)
axes[1, 1].bar(range(lags+1), acf_vals, alpha=0.7)
axes[1, 1].set_title('ACF of Squared Standardized Residuals')
axes[1, 1].set_xlabel('Lag')
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Huấn luyện mô hình GARCH¶
# Fit mô hình GARCH(1,1) cho tập train
print("Đang huấn luyện mô hình GARCH(1,1)...")
fitted_garch, garch_model = build_garch_model(train_returns, p=1, q=1)
# Đánh giá mô hình
aic, bic = evaluate_garch_model(fitted_garch)
# Vẽ biểu đồ chẩn đoán với train_returns và train_data index
plot_garch_diagnostics(fitted_garch, train_returns, train_data.index)
Đang huấn luyện mô hình GARCH(1,1)...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -7268.61
Distribution: Normal AIC: 14545.2
Method: Maximum Likelihood BIC: 14568.3
No. Observations: 2358
Date: Tue, Jun 03 2025 Df Residuals: 2357
Time: 20:39:06 Df Model: 1
Mean Model
===========================================================================
coef std err t P>|t| 95.0% Conf. Int.
---------------------------------------------------------------------------
mu 0.1753 0.100 1.748 8.050e-02 [-2.128e-02, 0.372]
Volatility Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
omega 2.2080 0.846 2.610 9.052e-03 [ 0.550, 3.866]
alpha[1] 0.1239 3.341e-02 3.708 2.088e-04 [5.840e-02, 0.189]
beta[1] 0.8115 4.769e-02 17.016 6.253e-65 [ 0.718, 0.905]
==========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0058
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình trên tập test¶
# Dự đoán trên tập test
test_size_days = len(test_data)
last_train_price = train_data['Price'].iloc[-1]
# Forecast cho test period
test_forecast = fitted_garch.forecast(horizon=test_size_days)
forecast_returns = test_forecast.mean.iloc[-1].values / 100
forecast_volatility = np.sqrt(test_forecast.variance.iloc[-1].values) / 100
# Tính predicted prices cho test set
predicted_prices_test = []
current_price = last_train_price
for i in range(test_size_days):
# Sử dụng predicted return
predicted_return = forecast_returns[i]
current_price = current_price * np.exp(predicted_return)
predicted_prices_test.append(current_price)
predicted_prices_test = np.array(predicted_prices_test)
# Tính metrics
actual_test_prices = test_data['Price'].values
mape = mean_absolute_percentage_error(actual_test_prices, predicted_prices_test)
mse = mean_squared_error(actual_test_prices, predicted_prices_test)
rmse = np.sqrt(mse)
print(f'Kết quả đánh giá mô hình GARCH(1,1):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'AIC: {aic:.2f}')
print(f'BIC: {bic:.2f}')
# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data.index, actual_test_prices, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (7:3)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1): MAPE: 1.01% MSE: 9081335.20 RMSE: 3013.53 AIC: 14545.22 BIC: 14568.28
Dự đoán tương lai¶
# Dự đoán 30, 60, 90 ngày tiếp theo
last_price = data_clean['Price'].iloc[-1]
# Forecast prices
forecast_30, vol_30 = forecast_garch_prices(fitted_garch, last_price, 30)
forecast_60, vol_60 = forecast_garch_prices(fitted_garch, last_price, 60)
forecast_90, vol_90 = forecast_garch_prices(fitted_garch, last_price, 90)
# Tạo dates cho forecasts
forecast_dates_30 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
# Tạo DataFrames
forecast_df_30 = pd.DataFrame(forecast_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecast_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecast_90, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa
plt.figure(figsize=(16, 10))
# Vẽ dữ liệu lịch sử
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán test
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán trên test set', color='orange', linewidth=2, alpha=0.8)
# Vẽ dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
label='Dự đoán 30 ngày', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'],
label='Dự đoán 60 ngày', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'],
label='Dự đoán 90 ngày', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Đường phân cách
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Ethereum bằng GARCH(1,1) - Split 7:3', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f'\nDự đoán giá Ethereum 30 ngày tiếp theo (GARCH 7:3):')
print(f'Giá cao nhất: ${forecast_30.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30.min():.2f}')
print(f'Giá trung bình: ${forecast_30.mean():.2f}')
print(f'Volatility trung bình: {vol_30.mean():.2f}%')
Dự đoán giá Ethereum 30 ngày tiếp theo (GARCH 7:3): Giá cao nhất: $3216.78 Giá thấp nhất: $1904.24 Giá trung bình: $2542.63 Volatility trung bình: 5.39%
Chia 8:2¶
Chuẩn bị dữ liệu 8:2¶
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3369 dòng
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data_clean) * 0.8)
train_data_82 = data_clean.iloc[0:train_size_82].copy()
test_data_82 = data_clean.iloc[train_size_82:].copy()
# Lấy returns cho train và test
train_returns_82 = train_data_82['Log_Return'].values
test_returns_82 = test_data_82['Log_Return'].values
print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2695 Kích thước tập test 8:2: 674
Huấn luyện mô hình GARCH 8:2¶
# Fit mô hình GARCH(1,1) cho split 8:2
print("Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...")
fitted_garch_82, garch_model_82 = build_garch_model(train_returns_82, p=1, q=1)
# Đánh giá mô hình
aic_82, bic_82 = evaluate_garch_model(fitted_garch_82)
Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -8145.50
Distribution: Normal AIC: 16299.0
Method: Maximum Likelihood BIC: 16322.6
No. Observations: 2695
Date: Tue, Jun 03 2025 Df Residuals: 2694
Time: 20:39:08 Df Model: 1
Mean Model
===========================================================================
coef std err t P>|t| 95.0% Conf. Int.
---------------------------------------------------------------------------
mu 0.1537 8.709e-02 1.765 7.763e-02 [-1.701e-02, 0.324]
Volatility Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
omega 1.2652 0.564 2.243 2.493e-02 [ 0.159, 2.371]
alpha[1] 0.1237 3.420e-02 3.619 2.960e-04 [5.672e-02, 0.191]
beta[1] 0.8401 4.303e-02 19.523 6.957e-85 [ 0.756, 0.924]
==========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0051
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình 8:2¶
# Dự đoán trên tập test 8:2
test_size_days_82 = len(test_data_82)
last_train_price_82 = train_data_82['Price'].iloc[-1]
# Forecast cho test period
test_forecast_82 = fitted_garch_82.forecast(horizon=test_size_days_82)
forecast_returns_82 = test_forecast_82.mean.iloc[-1].values / 100
# Tính predicted prices cho test set 8:2
predicted_prices_test_82 = []
current_price_82 = last_train_price_82
for i in range(test_size_days_82):
predicted_return = forecast_returns_82[i]
current_price_82 = current_price_82 * np.exp(predicted_return)
predicted_prices_test_82.append(current_price_82)
predicted_prices_test_82 = np.array(predicted_prices_test_82)
# Tính metrics cho 8:2
actual_test_prices_82 = test_data_82['Price'].values
mape_82 = mean_absolute_percentage_error(actual_test_prices_82, predicted_prices_test_82)
mse_82 = mean_squared_error(actual_test_prices_82, predicted_prices_test_82)
rmse_82 = np.sqrt(mse_82)
print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'AIC: {aic_82:.2f}')
print(f'BIC: {bic_82:.2f}')
Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2: MAPE: 0.39% MSE: 1602692.67 RMSE: 1265.97 AIC: 16298.99 BIC: 16322.59
Dự đoán tương lai 8:2¶
# Dự đoán 30, 60, 90 ngày tiếp theo cho 8:2
forecast_30_82, vol_30_82 = forecast_garch_prices(fitted_garch_82, last_price, 30)
forecast_60_82, vol_60_82 = forecast_garch_prices(fitted_garch_82, last_price, 60)
forecast_90_82, vol_90_82 = forecast_garch_prices(fitted_garch_82, last_price, 90)
# Tạo DataFrames cho 8:2
forecast_df_30_82 = pd.DataFrame(forecast_30_82, index=forecast_dates_30, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecast_60_82, index=forecast_dates_60, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecast_90_82, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa 8:2
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_82.index, predicted_prices_test_82, label='Dự đoán trên test set (8:2)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
label='Dự đoán 30 ngày (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
label='Dự đoán 60 ngày (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
label='Dự đoán 90 ngày (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Ethereum bằng GARCH(1,1) - Split 8:2', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f'\nDự đoán giá Ethereum 30 ngày tiếp theo (GARCH 8:2):')
print(f'Giá cao nhất: ${forecast_30_82.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30_82.min():.2f}')
print(f'Giá trung bình: ${forecast_30_82.mean():.2f}')
print(f'Volatility trung bình: {vol_30_82.mean():.2f}%')
Dự đoán giá Ethereum 30 ngày tiếp theo (GARCH 8:2): Giá cao nhất: $3007.28 Giá thấp nhất: $1942.20 Giá trung bình: $2483.53 Volatility trung bình: 4.34%
Chia 9:1¶
Chuẩn bị dữ liệu 9:1¶
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3369 dòng
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data_clean) * 0.9)
train_data_91 = data_clean.iloc[0:train_size_91].copy()
test_data_91 = data_clean.iloc[train_size_91:].copy()
# Lấy returns cho train và test
train_returns_91 = train_data_91['Log_Return'].values
test_returns_91 = test_data_91['Log_Return'].values
print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3032 Kích thước tập test 9:1: 337
Huấn luyện mô hình GARCH 9:1¶
# Fit mô hình GARCH(1,1) cho split 9:1
print("Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...")
fitted_garch_91, garch_model_91 = build_garch_model(train_returns_91, p=1, q=1)
# Đánh giá mô hình
aic_91, bic_91 = evaluate_garch_model(fitted_garch_91)
Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -8982.92
Distribution: Normal AIC: 17973.8
Method: Maximum Likelihood BIC: 17997.9
No. Observations: 3032
Date: Tue, Jun 03 2025 Df Residuals: 3031
Time: 20:39:09 Df Model: 1
Mean Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
mu 0.1493 7.617e-02 1.960 4.998e-02 [1.055e-05, 0.299]
Volatility Model
===========================================================================
coef std err t P>|t| 95.0% Conf. Int.
---------------------------------------------------------------------------
omega 0.6975 0.379 1.839 6.588e-02 [-4.579e-02, 1.441]
alpha[1] 0.1120 3.552e-02 3.154 1.612e-03 [4.240e-02, 0.182]
beta[1] 0.8694 4.112e-02 21.145 3.051e-99 [ 0.789, 0.950]
===========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0019
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình 9:1¶
# Dự đoán trên tập test 9:1
test_size_days_91 = len(test_data_91)
last_train_price_91 = train_data_91['Price'].iloc[-1]
# Forecast cho test period
test_forecast_91 = fitted_garch_91.forecast(horizon=test_size_days_91)
forecast_returns_91 = test_forecast_91.mean.iloc[-1].values / 100
# Tính predicted prices cho test set 9:1
predicted_prices_test_91 = []
current_price_91 = last_train_price_91
for i in range(test_size_days_91):
predicted_return = forecast_returns_91[i]
current_price_91 = current_price_91 * np.exp(predicted_return)
predicted_prices_test_91.append(current_price_91)
predicted_prices_test_91 = np.array(predicted_prices_test_91)
# Tính metrics cho 9:1
actual_test_prices_91 = test_data_91['Price'].values
mape_91 = mean_absolute_percentage_error(actual_test_prices_91, predicted_prices_test_91)
mse_91 = mean_squared_error(actual_test_prices_91, predicted_prices_test_91)
rmse_91 = np.sqrt(mse_91)
print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'AIC: {aic_91:.2f}')
print(f'BIC: {bic_91:.2f}')
Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1: MAPE: 0.74% MSE: 3956901.43 RMSE: 1989.20 AIC: 17973.83 BIC: 17997.90
Dự đoán tương lai 9:1¶
# Dự đoán 30, 60, 90 ngày tiếp theo cho 9:1
forecast_30_91, vol_30_91 = forecast_garch_prices(fitted_garch_91, last_price, 30)
forecast_60_91, vol_60_91 = forecast_garch_prices(fitted_garch_91, last_price, 60)
forecast_90_91, vol_90_91 = forecast_garch_prices(fitted_garch_91, last_price, 90)
# Tạo DataFrames cho 9:1
forecast_df_30_91 = pd.DataFrame(forecast_30_91, index=forecast_dates_30, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecast_60_91, index=forecast_dates_60, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecast_90_91, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa 9:1
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_91.index, predicted_prices_test_91, label='Dự đoán trên test set (9:1)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
label='Dự đoán 30 ngày (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
label='Dự đoán 60 ngày (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
label='Dự đoán 90 ngày (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá Ethereum bằng GARCH(1,1) - Split 9:1', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f'\nDự đoán giá Ethereum 30 ngày tiếp theo (GARCH 9:1):')
print(f'Giá cao nhất: ${forecast_30_91.max():.2f}')
print(f'Giá thấp nhất: ${forecast_30_91.min():.2f}')
print(f'Giá trung bình: ${forecast_30_91.mean():.2f}')
print(f'Volatility trung bình: {vol_30_91.mean():.2f}%')
Dự đoán giá Ethereum 30 ngày tiếp theo (GARCH 9:1): Giá cao nhất: $2961.26 Giá thấp nhất: $2020.33 Giá trung bình: $2500.42 Volatility trung bình: 3.86%
So sánh 3 tỉ lệ GARCH¶
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu cho GARCH
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH MODEL")
print("="*80)
# Thu thập thông tin từ 3 splits
garch_splits_info = {
'7:3': {
'train_size': len(train_data),
'test_size': len(test_data),
'mape': mape,
'mse': mse,
'rmse': rmse,
'aic': aic,
'bic': bic,
'avg_volatility': vol_30.mean()
},
'8:2': {
'train_size': len(train_data_82),
'test_size': len(test_data_82),
'mape': mape_82,
'mse': mse_82,
'rmse': rmse_82,
'aic': aic_82,
'bic': bic_82,
'avg_volatility': vol_30_82.mean()
},
'9:1': {
'train_size': len(train_data_91),
'test_size': len(test_data_91),
'mape': mape_91,
'mse': mse_91,
'rmse': rmse_91,
'aic': aic_91,
'bic': bic_91,
'avg_volatility': vol_30_91.mean()
}
}
# In bảng so sánh GARCH
for split, info in garch_splits_info.items():
print(f"\n{split} Split (GARCH):")
print(f" Kích thước train: {info['train_size']:,} mẫu")
print(f" Kích thước test: {info['test_size']:,} mẫu")
print(f" MAPE: {info['mape']:.2f}%")
print(f" MSE: {info['mse']:,.2f}")
print(f" RMSE: {info['rmse']:,.2f}")
print(f" AIC: {info['aic']:.2f}")
print(f" BIC: {info['bic']:.2f}")
print(f" Avg Volatility: {info['avg_volatility']:.2f}%")
================================================================================ SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH MODEL ================================================================================ 7:3 Split (GARCH): Kích thước train: 2,358 mẫu Kích thước test: 1,011 mẫu MAPE: 1.01% MSE: 9,081,335.20 RMSE: 3,013.53 AIC: 14545.22 BIC: 14568.28 Avg Volatility: 5.39% 8:2 Split (GARCH): Kích thước train: 2,695 mẫu Kích thước test: 674 mẫu MAPE: 0.39% MSE: 1,602,692.67 RMSE: 1,265.97 AIC: 16298.99 BIC: 16322.59 Avg Volatility: 4.34% 9:1 Split (GARCH): Kích thước train: 3,032 mẫu Kích thước test: 337 mẫu MAPE: 0.74% MSE: 3,956,901.43 RMSE: 1,989.20 AIC: 17973.83 BIC: 17997.90 Avg Volatility: 3.86%
# Vẽ biểu đồ so sánh các metrics cho GARCH
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
# 1. So sánh MAPE
mape_values = [garch_splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%) - GARCH', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 2. So sánh RMSE
rmse_values = [garch_splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD) - GARCH', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
axes[0, 1].text(i, v + 200, f'{v:,.0f}', ha='center', va='bottom', fontweight='bold')
# 3. So sánh AIC
aic_values = [garch_splits_info[split]['aic'] for split in splits]
axes[0, 2].bar(splits, aic_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh AIC - GARCH', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('AIC')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(aic_values):
axes[0, 2].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')
# 4. So sánh BIC
bic_values = [garch_splits_info[split]['bic'] for split in splits]
axes[1, 0].bar(splits, bic_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh BIC - GARCH', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('BIC')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(bic_values):
axes[1, 0].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')
# 5. So sánh Average Volatility
vol_values = [garch_splits_info[split]['avg_volatility'] for split in splits]
axes[1, 1].bar(splits, vol_values, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Avg Volatility (%) - GARCH', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('Volatility (%)')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(vol_values):
axes[1, 1].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 6. So sánh kích thước test set
test_sizes = [garch_splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set - GARCH', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')
plt.tight_layout()
plt.show()
# Tạo DataFrame tổng hợp kết quả GARCH
garch_comparison_df = pd.DataFrame({
'Split': ['7:3', '8:2', '9:1'],
'Train_Size': [garch_splits_info[split]['train_size'] for split in splits],
'Test_Size': [garch_splits_info[split]['test_size'] for split in splits],
'MAPE (%)': [garch_splits_info[split]['mape'] for split in splits],
'RMSE (USD)': [garch_splits_info[split]['rmse'] for split in splits],
'MSE': [garch_splits_info[split]['mse'] for split in splits],
'AIC': [garch_splits_info[split]['aic'] for split in splits],
'BIC': [garch_splits_info[split]['bic'] for split in splits],
'Avg_Volatility (%)': [garch_splits_info[split]['avg_volatility'] for split in splits]
})
print("\nBẢNG TỔNG HỢP KẾT QUẢ GARCH:")
print("="*100)
print(garch_comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ GARCH: ==================================================================================================== Split Train_Size Test_Size MAPE (%) RMSE (USD) MSE AIC BIC Avg_Volatility (%) 7:3 2358 1011 1.0094 3013.5254 9081335.1999 14545.2183 14568.2806 5.3929 8:2 2695 674 0.3860 1265.9750 1602692.6673 16298.9918 16322.5884 4.3418 9:1 3032 337 0.7437 1989.1962 3956901.4280 17973.8326 17997.9006 3.8590
# Phân tích và đưa ra khuyến nghị cho GARCH
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH MODEL")
print("="*80)
# Tìm split tốt nhất cho từng metric
best_mape_split_garch = splits[np.argmin([garch_splits_info[split]['mape'] for split in splits])]
best_rmse_split_garch = splits[np.argmin([garch_splits_info[split]['rmse'] for split in splits])]
best_aic_split_garch = splits[np.argmin([garch_splits_info[split]['aic'] for split in splits])]
best_bic_split_garch = splits[np.argmin([garch_splits_info[split]['bic'] for split in splits])]
print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH):")
print(f" • Tốt nhất theo MAPE: {best_mape_split_garch} ({garch_splits_info[best_mape_split_garch]['mape']:.2f}%)")
print(f" • Tốt nhất theo RMSE: {best_rmse_split_garch} ({garch_splits_info[best_rmse_split_garch]['rmse']:,.2f} USD)")
print(f" • Tốt nhất theo AIC: {best_aic_split_garch} ({garch_splits_info[best_aic_split_garch]['aic']:.2f})")
print(f" • Tốt nhất theo BIC: {best_bic_split_garch} ({garch_splits_info[best_bic_split_garch]['bic']:.2f})")
# Tính điểm tổng hợp cho GARCH
def calculate_garch_rank_score(splits_info):
scores = {}
splits_list = list(splits_info.keys())
# Rank cho các metrics (thấp hơn = tốt hơn)
mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
aic_rank = sorted(splits_list, key=lambda x: splits_info[x]['aic'])
bic_rank = sorted(splits_list, key=lambda x: splits_info[x]['bic'])
for split in splits_list:
# Điểm rank với trọng số cho GARCH
score = (mape_rank.index(split) + 1) * 0.35 + \
(rmse_rank.index(split) + 1) * 0.35 + \
(aic_rank.index(split) + 1) * 0.15 + \
(bic_rank.index(split) + 1) * 0.15
scores[split] = score
return scores
# Tính điểm tổng hợp
garch_scores = calculate_garch_rank_score(garch_splits_info)
best_overall_split_garch = min(garch_scores, key=garch_scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP GARCH (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):")
for split in garch_splits_info.keys():
print(f" • {split}: {garch_scores[split]:.2f} điểm")
print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH:")
print(f" 🏆 MÔ HÌNH GARCH TỐT NHẤT: Split {best_overall_split_garch}")
print(f" 📊 Lý do:")
print(f" - MAPE: {garch_splits_info[best_overall_split_garch]['mape']:.2f}%")
print(f" - RMSE: {garch_splits_info[best_overall_split_garch]['rmse']:,.2f} USD")
print(f" - AIC: {garch_splits_info[best_overall_split_garch]['aic']:.2f}")
print(f" - BIC: {garch_splits_info[best_overall_split_garch]['bic']:.2f}")
print(f" - Avg Volatility: {garch_splits_info[best_overall_split_garch]['avg_volatility']:.2f}%")
print(f" - Tập test có {garch_splits_info[best_overall_split_garch]['test_size']:,} mẫu")
print(f"\n4. NHẬN XÉT VỀ GARCH MODEL:")
print(" • GARCH hiệu quả trong việc mô hình hóa volatility clustering")
print(" • Phù hợp với dữ liệu tài chính có tính heteroskedasticity")
print(" • Cung cấp forecast về volatility cùng với price prediction")
print(" • AIC và BIC giúp đánh giá model fit và complexity")
print(f"\n ⚠️ LƯU Ý GARCH: Model này tập trung vào volatility modeling")
print(f" và phù hợp cho short-term forecasting với dữ liệu có tính volatility cao như Ethereum.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH MODEL
================================================================================
1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH):
• Tốt nhất theo MAPE: 8:2 (0.39%)
• Tốt nhất theo RMSE: 8:2 (1,265.97 USD)
• Tốt nhất theo AIC: 7:3 (14545.22)
• Tốt nhất theo BIC: 7:3 (14568.28)
2. ĐIỂM TỔNG HỢP GARCH (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):
• 7:3: 2.40 điểm
• 8:2: 1.30 điểm
• 9:1: 2.30 điểm
3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH:
🏆 MÔ HÌNH GARCH TỐT NHẤT: Split 8:2
📊 Lý do:
- MAPE: 0.39%
- RMSE: 1,265.97 USD
- AIC: 16298.99
- BIC: 16322.59
- Avg Volatility: 4.34%
- Tập test có 674 mẫu
4. NHẬN XÉT VỀ GARCH MODEL:
• GARCH hiệu quả trong việc mô hình hóa volatility clustering
• Phù hợp với dữ liệu tài chính có tính heteroskedasticity
• Cung cấp forecast về volatility cùng với price prediction
• AIC và BIC giúp đánh giá model fit và complexity
⚠️ LƯU Ý GARCH: Model này tập trung vào volatility modeling
và phù hợp cho short-term forecasting với dữ liệu có tính volatility cao như Ethereum.
# Vẽ so sánh volatility patterns giữa các splits
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
# Plot conditional volatility cho từng split
fitted_models = [fitted_garch, fitted_garch_82, fitted_garch_91]
train_data_list = [train_data, train_data_82, train_data_91]
split_names = ['7:3', '8:2', '9:1']
for i, (model, train_data_split, split_name) in enumerate(zip(fitted_models, train_data_list, split_names)):
axes[i].plot(train_data_split.index, train_data_split['Log_Return'] * 100,
alpha=0.7, label='Returns (%)', color='lightblue')
axes[i].plot(train_data_split.index, model.conditional_volatility,
color='red', linewidth=2, label='Conditional Volatility')
axes[i].set_title(f'Volatility Pattern - Split {split_name}', fontweight='bold')
axes[i].set_ylabel('Returns (%) / Volatility')
axes[i].legend()
axes[i].grid(True, alpha=0.3)
axes[i].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
# Vẽ biểu đồ radar cho so sánh tổng thể GARCH
def create_garch_radar_chart():
# Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
metrics = ['MAPE', 'RMSE', 'AIC', 'BIC', 'Test_Size']
# Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
data = {}
for split in splits:
mape_norm = 1 - (garch_splits_info[split]['mape'] - min([garch_splits_info[s]['mape'] for s in splits])) / \
(max([garch_splits_info[s]['mape'] for s in splits]) - min([garch_splits_info[s]['mape'] for s in splits]))
rmse_norm = 1 - (garch_splits_info[split]['rmse'] - min([garch_splits_info[s]['rmse'] for s in splits])) / \
(max([garch_splits_info[s]['rmse'] for s in splits]) - min([garch_splits_info[s]['rmse'] for s in splits]))
aic_norm = 1 - (garch_splits_info[split]['aic'] - min([garch_splits_info[s]['aic'] for s in splits])) / \
(max([garch_splits_info[s]['aic'] for s in splits]) - min([garch_splits_info[s]['aic'] for s in splits]))
bic_norm = 1 - (garch_splits_info[split]['bic'] - min([garch_splits_info[s]['bic'] for s in splits])) / \
(max([garch_splits_info[s]['bic'] for s in splits]) - min([garch_splits_info[s]['bic'] for s in splits]))
test_size_norm = (garch_splits_info[split]['test_size'] - min([garch_splits_info[s]['test_size'] for s in splits])) / \
(max([garch_splits_info[s]['test_size'] for s in splits]) - min([garch_splits_info[s]['test_size'] for s in splits]))
data[split] = [mape_norm, rmse_norm, aic_norm, bic_norm, test_size_norm]
# Tạo radar chart
angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
angles += angles[:1] # Complete the circle
fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
for i, (split, values) in enumerate(data.items()):
values += values[:1] # Complete the circle
ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
ax.fill(angles, values, alpha=0.25, color=colors[i])
ax.set_xticks(angles[:-1])
ax.set_xticklabels(metrics)
ax.set_ylim(0, 1)
ax.set_title('So sánh tổng thể các Split GARCH (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
ax.grid(True)
plt.tight_layout()
plt.show()
create_garch_radar_chart()
Kết luận cuối cùng - GARCH Model¶
Dựa trên phân tích toàn diện các tiêu chí đánh giá, mô hình GARCH với split tỉ lệ dữ liệu tốt nhất đã được xác định.
Các yếu tố được xem xét cho GARCH:
- MAPE (Mean Absolute Percentage Error): Đo lường độ chính xác dự đoán giá
- RMSE (Root Mean Square Error): Đo lường sai số tuyệt đối
- AIC (Akaike Information Criterion): Đánh giá model fit và complexity
- BIC (Bayesian Information Criterion): Đánh giá model với penalty cho complexity
- Volatility Forecasting: Khả năng dự đoán volatility (điểm mạnh của GARCH)
- Kích thước tập test: Đảm bảo độ tin cậy trong đánh giá
Ưu điểm của GARCH:
- Mô hình hóa hiệu quả volatility clustering trong dữ liệu tài chính
- Cung cấp forecast cho cả price và volatility
- Phù hợp với tính chất heteroskedasticity của Ethereum
- Cho phép risk assessment thông qua volatility predictions
Khuyến nghị sử dụng: Mô hình GARCH với tỉ lệ chia dữ liệu được đánh giá cao nhất sẽ được sử dụng cho các dự đoán cuối cùng về giá và volatility Ethereum, đặc biệt hiệu quả cho short-term forecasting và risk management.
XRP¶
Import thư viện¶
%pip install arch seaborn
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import matplotlib.pyplot as plt
import math
from arch import arch_model
from arch.univariate import GARCH, ConstantMean
import warnings
warnings.filterwarnings('ignore')
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.stats.diagnostic import acorr_ljungbox
from scipy import stats
from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
from typing import Tuple
import seaborn as sns
# Thêm import cho kiểm tra tính dừng
from statsmodels.tsa.stattools import adfuller
Requirement already satisfied: arch in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (7.2.0) Requirement already satisfied: seaborn in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (0.13.2) Requirement already satisfied: numpy>=1.22.3 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.1.3) Requirement already satisfied: scipy>=1.8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (1.15.3) Requirement already satisfied: pandas>=1.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (2.2.3) Requirement already satisfied: statsmodels>=0.12 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from arch) (0.14.4) Requirement already satisfied: matplotlib!=3.6.1,>=3.4 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from seaborn) (3.10.3) Requirement already satisfied: contourpy>=1.0.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.3.2) Requirement already satisfied: cycler>=0.10 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (4.58.1) Requirement already satisfied: kiwisolver>=1.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (1.4.8) Requirement already satisfied: packaging>=20.0 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (25.0) Requirement already satisfied: pillow>=8 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (11.2.1) Requirement already satisfied: pyparsing>=2.3.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (3.2.3) Requirement already satisfied: python-dateutil>=2.7 in c:\users\hii\appdata\roaming\python\python311\site-packages (from matplotlib!=3.6.1,>=3.4->seaborn) (2.9.0.post0) Requirement already satisfied: pytz>=2020.1 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2) Requirement already satisfied: tzdata>=2022.7 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from pandas>=1.4->arch) (2025.2) Requirement already satisfied: patsy>=0.5.6 in c:\users\hii\appdata\local\programs\python\python311\lib\site-packages (from statsmodels>=0.12->arch) (1.0.1) Requirement already satisfied: six>=1.5 in c:\users\hii\appdata\roaming\python\python311\site-packages (from python-dateutil>=2.7->matplotlib!=3.6.1,>=3.4->seaborn) (1.17.0) Note: you may need to restart the kernel to use updated packages.
[notice] A new release of pip is available: 24.0 -> 25.1.1 [notice] To update, run: python.exe -m pip install --upgrade pip
XRP Dataset¶
Import csv¶
# Đọc file XRP
file_path = "D:\\github_desktop\\Cryptocurrency-Price-Prediction\\Cryptocurrency\\Dataset\\XRP Historical Data.csv"
data = pd.read_csv(file_path)
# Loại bỏ dấu phẩy và chuyển đổi thành float cho các cột Price và Open
for col in ['Price', 'Open']:
data[col] = data[col].astype(str).str.replace(',', '', regex=False).astype(float)
# Hàm xử lý cột 'Vol.' chứa hậu tố 'K', 'M', 'B'
def convert_volume(val):
val = str(val).replace(',', '').strip()
if 'K' in val:
return float(val.replace('K', '')) * 1_000
elif 'M' in val:
return float(val.replace('M', '')) * 1_000_000
elif 'B' in val:
return float(val.replace('B', '')) * 1_000_000_000
else:
try:
return float(val)
except ValueError:
return np.nan # Nếu chuỗi rỗng hoặc không hợp lệ
# Áp dụng xử lý cho cột 'Vol.'
data['Vol.'] = data['Vol.'].apply(convert_volume)
# Kiểm tra NaN trước xử lý
print(f"Trước khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")
# Nội suy và điền 0 nếu còn thiếu
data['Vol.'] = data['Vol.'].interpolate(method='linear')
data['Vol.'] = data['Vol.'].fillna(0)
# Kiểm tra NaN sau xử lý
print(f"Sau khi xử lý, số NaN ở Vol.: {data['Vol.'].isna().sum()}")
# Chuyển cột Date sang datetime và đặt làm index
data['Date'] = pd.to_datetime(data['Date'])
data.set_index('Date', inplace=True)
data.sort_index(inplace=True)
# Select 3 columns: Price, Open, Vol
data_features = data[['Price', 'Open', 'Vol.']].copy()
print("Data shape:", data.shape)
print("Columns:", data.columns.tolist())
print("\nFirst 5 rows:")
print(data[['Price', 'Open', 'Vol.']].head())
print(f"Tổng số dữ liệu: {len(data)} dòng")
Trước khi xử lý, số NaN ở Vol.: 12
Sau khi xử lý, số NaN ở Vol.: 0
Data shape: (3369, 6)
Columns: ['Price', 'Open', 'High', 'Low', 'Vol.', 'Change %']
First 5 rows:
Price Open Vol.
Date
2016-03-10 0.0082 0.0081 59130.0
2016-03-11 0.0092 0.0082 25820.0
2016-03-12 0.0081 0.0092 78230.0
2016-03-13 0.0082 0.0081 620.0
2016-03-14 0.0083 0.0082 19310.0
Tổng số dữ liệu: 3369 dòng
Chia 7:3¶
Chuẩn bị dữ liệu cho GARCH¶
# Tính log returns cho GARCH model
data['Log_Return'] = np.log(data['Price'] / data['Price'].shift(1))
data['Simple_Return'] = data['Price'].pct_change()
# Loại bỏ giá trị NaN
data_clean = data.dropna()
print(f"Dữ liệu sau khi tính returns: {len(data_clean)} dòng")
print("\nThống kê mô tả của Log Returns:")
print(data_clean['Log_Return'].describe())
# Kiểm tra tính dừng của chuỗi returns
from statsmodels.tsa.stattools import adfuller
def check_stationarity(series, name):
result = adfuller(series.dropna())
print(f'\n{name} - Kiểm định ADF:')
print(f'ADF Statistic: {result[0]:.6f}')
print(f'p-value: {result[1]:.6f}')
print(f'Critical Values:')
for key, value in result[4].items():
print(f'\t{key}: {value:.3f}')
if result[1] <= 0.05:
print("✓ Chuỗi dừng (stationary)")
else:
print("✗ Chuỗi không dừng (non-stationary)")
return result[1] <= 0.05
# Kiểm tra tính dừng
is_stationary = check_stationarity(data_clean['Log_Return'], 'Log Returns')
Dữ liệu sau khi tính returns: 3368 dòng Thống kê mô tả của Log Returns: count 3368.000000 mean 0.001652 std 0.064184 min -0.653301 25% -0.021956 50% 0.000000 75% 0.020798 max 1.027995 Name: Log_Return, dtype: float64 Log Returns - Kiểm định ADF: ADF Statistic: -12.279138 p-value: 0.000000 Critical Values: 1%: -3.432 5%: -2.862 10%: -2.567 ✓ Chuỗi dừng (stationary)
# Chia dữ liệu train/test theo tỷ lệ 7:3
train_size = int(len(data_clean) * 0.7)
train_data = data_clean.iloc[0:train_size].copy()
test_data = data_clean.iloc[train_size:].copy()
# Lấy returns cho train và test
train_returns = train_data['Log_Return'].values
test_returns = test_data['Log_Return'].values
print(f"Kích thước tập train: {len(train_data)}")
print(f"Kích thước tập test: {len(test_data)}")
print(f"Train returns shape: {train_returns.shape}")
print(f"Test returns shape: {test_returns.shape}")
Kích thước tập train: 2357 Kích thước tập test: 1011 Train returns shape: (2357,) Test returns shape: (1011,)
Xây dựng mô hình GARCH¶
def build_garch_model(returns_data, p=1, q=1):
"""
Xây dựng mô hình GARCH(p,q)
Args:
returns_data: Chuỗi returns
p: Order của GARCH term
q: Order của ARCH term
Returns:
Fitted GARCH model
"""
# Nhân returns với 100 để có scale phù hợp
returns_scaled = returns_data * 100
# Tạo mô hình GARCH
model = arch_model(returns_scaled, vol='Garch', p=p, q=q, dist='normal')
# Fit mô hình
fitted_model = model.fit(disp='off')
return fitted_model, model
def evaluate_garch_model(fitted_model):
"""
Đánh giá mô hình GARCH
"""
print("="*50)
print("THÔNG TIN MÔ HÌNH GARCH")
print("="*50)
print(fitted_model.summary())
# Kiểm tra phần dư
residuals = fitted_model.resid
standardized_residuals = residuals / fitted_model.conditional_volatility
# Ljung-Box test cho phần dư
lb_result = acorr_ljungbox(residuals, lags=10, return_df=True)
print(f"\nLjung-Box test p-value (residuals): {lb_result['lb_pvalue'].iloc[-1]:.4f}")
# Ljung-Box test cho phần dư bình phương
lb_result_sq = acorr_ljungbox(residuals**2, lags=10, return_df=True)
print(f"Ljung-Box test p-value (squared residuals): {lb_result_sq['lb_pvalue'].iloc[-1]:.4f}")
return fitted_model.aic, fitted_model.bic
def forecast_garch_prices(fitted_model, initial_price, forecast_horizon):
"""
Dự đoán giá sử dụng GARCH model
Args:
fitted_model: Mô hình GARCH đã fit
initial_price: Giá ban đầu
forecast_horizon: Số ngày dự đoán
Returns:
Dự đoán giá và volatility
"""
# Forecast returns và volatility
forecast = fitted_model.forecast(horizon=forecast_horizon)
# Lấy mean và variance dự đoán
forecast_mean = forecast.mean.iloc[-1].values / 100 # Scale về decimal
forecast_variance = forecast.variance.iloc[-1].values / 10000 # Scale về decimal
# Tạo random walks cho price prediction
np.random.seed(42) # Để kết quả reproducible
predicted_prices = []
current_price = initial_price
for i in range(forecast_horizon):
# Sử dụng mean return và add random noise based on predicted volatility
return_pred = forecast_mean[i] + np.random.normal(0, np.sqrt(forecast_variance[i]))
current_price = current_price * np.exp(return_pred)
predicted_prices.append(current_price)
return np.array(predicted_prices), np.sqrt(forecast_variance) * 100
def plot_garch_diagnostics(fitted_model, returns_data, train_data_index):
"""
Vẽ biểu đồ chẩn đoán cho mô hình GARCH
"""
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# 1. Returns và Conditional Volatility
# Sử dụng index từ train_data thay vì fitted_model.resid.index
dates = train_data_index
axes[0, 0].plot(dates, returns_data * 100, alpha=0.7, label='Returns (%)')
axes[0, 0].plot(dates, fitted_model.conditional_volatility, color='red', label='Conditional Volatility')
axes[0, 0].set_title('Returns và Conditional Volatility')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
axes[0, 0].tick_params(axis='x', rotation=45)
# 2. Standardized Residuals
std_resid = fitted_model.resid / fitted_model.conditional_volatility
axes[0, 1].plot(dates, std_resid)
axes[0, 1].set_title('Standardized Residuals')
axes[0, 1].axhline(y=0, color='red', linestyle='--', alpha=0.7)
axes[0, 1].grid(True, alpha=0.3)
axes[0, 1].tick_params(axis='x', rotation=45)
# 3. Q-Q Plot
stats.probplot(std_resid, dist="norm", plot=axes[1, 0])
axes[1, 0].set_title('Q-Q Plot of Standardized Residuals')
axes[1, 0].grid(True, alpha=0.3)
# 4. ACF of Squared Residuals
from statsmodels.tsa.stattools import acf
squared_resid = std_resid ** 2
lags = 20
acf_vals = acf(squared_resid, nlags=lags)
axes[1, 1].bar(range(lags+1), acf_vals, alpha=0.7)
axes[1, 1].set_title('ACF of Squared Standardized Residuals')
axes[1, 1].set_xlabel('Lag')
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Huấn luyện mô hình GARCH¶
# Fit mô hình GARCH(1,1) cho tập train
print("Đang huấn luyện mô hình GARCH(1,1)...")
fitted_garch, garch_model = build_garch_model(train_returns, p=1, q=1)
# Đánh giá mô hình
aic, bic = evaluate_garch_model(fitted_garch)
# Vẽ biểu đồ chẩn đoán với train_returns và train_data index
plot_garch_diagnostics(fitted_garch, train_returns, train_data.index)
Đang huấn luyện mô hình GARCH(1,1)...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -7432.28
Distribution: Normal AIC: 14872.6
Method: Maximum Likelihood BIC: 14895.6
No. Observations: 2357
Date: Tue, Jun 03 2025 Df Residuals: 2356
Time: 20:39:16 Df Model: 1
Mean Model
===========================================================================
coef std err t P>|t| 95.0% Conf. Int.
---------------------------------------------------------------------------
mu -0.2764 0.121 -2.286 2.228e-02 [ -0.513,-3.938e-02]
Volatility Model
========================================================================
coef std err t P>|t| 95.0% Conf. Int.
------------------------------------------------------------------------
omega 4.6448 1.580 2.939 3.288e-03 [ 1.548, 7.742]
alpha[1] 0.4258 0.140 3.034 2.411e-03 [ 0.151, 0.701]
beta[1] 0.5742 9.992e-02 5.747 9.101e-09 [ 0.378, 0.770]
========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0000
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình trên tập test¶
# Dự đoán trên tập test
test_size_days = len(test_data)
last_train_price = train_data['Price'].iloc[-1]
# Forecast cho test period
test_forecast = fitted_garch.forecast(horizon=test_size_days)
forecast_returns = test_forecast.mean.iloc[-1].values / 100
forecast_volatility = np.sqrt(test_forecast.variance.iloc[-1].values) / 100
# Tính predicted prices cho test set
predicted_prices_test = []
current_price = last_train_price
for i in range(test_size_days):
# Sử dụng predicted return
predicted_return = forecast_returns[i]
current_price = current_price * np.exp(predicted_return)
predicted_prices_test.append(current_price)
predicted_prices_test = np.array(predicted_prices_test)
# Tính metrics
actual_test_prices = test_data['Price'].values
mape = mean_absolute_percentage_error(actual_test_prices, predicted_prices_test)
mse = mean_squared_error(actual_test_prices, predicted_prices_test)
rmse = np.sqrt(mse)
print(f'Kết quả đánh giá mô hình GARCH(1,1):')
print(f'MAPE: {mape:.2f}%')
print(f'MSE: {mse:.2f}')
print(f'RMSE: {rmse:.2f}')
print(f'AIC: {aic:.2f}')
print(f'BIC: {bic:.2f}')
# Vẽ so sánh dự đoán vs thực tế trên test set
plt.figure(figsize=(15, 8))
plt.plot(test_data.index, actual_test_prices, label='Giá thực tế', linewidth=2, color='blue')
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán GARCH', linewidth=2, color='red', alpha=0.8)
plt.title('So sánh dự đoán GARCH vs Giá thực tế trên tập test (7:3)', fontsize=14, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Kết quả đánh giá mô hình GARCH(1,1): MAPE: 0.75% MSE: 1.19 RMSE: 1.09 AIC: 14872.56 BIC: 14895.62
Dự đoán tương lai¶
# Dự đoán 30, 60, 90 ngày tiếp theo
last_price = data_clean['Price'].iloc[-1]
# Forecast prices
forecast_30, vol_30 = forecast_garch_prices(fitted_garch, last_price, 30)
forecast_60, vol_60 = forecast_garch_prices(fitted_garch, last_price, 60)
forecast_90, vol_90 = forecast_garch_prices(fitted_garch, last_price, 90)
# Tạo dates cho forecasts
forecast_dates_30 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=30, freq='D')
forecast_dates_60 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=60, freq='D')
forecast_dates_90 = pd.date_range(start=data_clean.index[-1] + pd.Timedelta(days=1), periods=90, freq='D')
# Tạo DataFrames
forecast_df_30 = pd.DataFrame(forecast_30, index=forecast_dates_30, columns=['Price'])
forecast_df_60 = pd.DataFrame(forecast_60, index=forecast_dates_60, columns=['Price'])
forecast_df_90 = pd.DataFrame(forecast_90, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa
plt.figure(figsize=(16, 10))
# Vẽ dữ liệu lịch sử
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
# Vẽ dự đoán test
plt.plot(test_data.index, predicted_prices_test, label='Dự đoán trên test set', color='orange', linewidth=2, alpha=0.8)
# Vẽ dự đoán tương lai
plt.plot(forecast_df_30.index, forecast_df_30['Price'],
label='Dự đoán 30 ngày', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60.index, forecast_df_60['Price'],
label='Dự đoán 60 ngày', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90.index, forecast_df_90['Price'],
label='Dự đoán 90 ngày', color='purple', linestyle='--', linewidth=2, alpha=0.3)
# Đường phân cách
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá XRP bằng GARCH(1,1) - Split 7:3', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f'\nDự đoán giá XRP 30 ngày tiếp theo (GARCH 7:3):')
print(f'Giá cao nhất: ${forecast_30.max():.4f}')
print(f'Giá thấp nhất: ${forecast_30.min():.4f}')
print(f'Giá trung bình: ${forecast_30.mean():.4f}')
print(f'Volatility trung bình: {vol_30.mean():.2f}%')
Dự đoán giá XRP 30 ngày tiếp theo (GARCH 7:3): Giá cao nhất: $2.7270 Giá thấp nhất: $0.9456 Giá trung bình: $1.8551 Volatility trung bình: 8.88%
Chia 8:2¶
Chuẩn bị dữ liệu 8:2¶
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3368 dòng
# Chia dữ liệu train/test theo tỷ lệ 8:2
train_size_82 = int(len(data_clean) * 0.8)
train_data_82 = data_clean.iloc[0:train_size_82].copy()
test_data_82 = data_clean.iloc[train_size_82:].copy()
# Lấy returns cho train và test
train_returns_82 = train_data_82['Log_Return'].values
test_returns_82 = test_data_82['Log_Return'].values
print(f"Kích thước tập train 8:2: {len(train_data_82)}")
print(f"Kích thước tập test 8:2: {len(test_data_82)}")
Kích thước tập train 8:2: 2694 Kích thước tập test 8:2: 674
Huấn luyện mô hình GARCH 8:2¶
# Fit mô hình GARCH(1,1) cho split 8:2
print("Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...")
fitted_garch_82, garch_model_82 = build_garch_model(train_returns_82, p=1, q=1)
# Đánh giá mô hình
aic_82, bic_82 = evaluate_garch_model(fitted_garch_82)
Đang huấn luyện mô hình GARCH(1,1) cho split 8:2...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -8465.86
Distribution: Normal AIC: 16939.7
Method: Maximum Likelihood BIC: 16963.3
No. Observations: 2694
Date: Tue, Jun 03 2025 Df Residuals: 2693
Time: 20:39:18 Df Model: 1
Mean Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
mu -0.1646 0.111 -1.477 0.140 [ -0.383,5.385e-02]
Volatility Model
========================================================================
coef std err t P>|t| 95.0% Conf. Int.
------------------------------------------------------------------------
omega 5.8760 2.221 2.645 8.163e-03 [ 1.522, 10.230]
alpha[1] 0.3580 0.124 2.893 3.819e-03 [ 0.115, 0.601]
beta[1] 0.5744 9.367e-02 6.132 8.663e-10 [ 0.391, 0.758]
========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0000
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình 8:2¶
# Dự đoán trên tập test 8:2
test_size_days_82 = len(test_data_82)
last_train_price_82 = train_data_82['Price'].iloc[-1]
# Forecast cho test period
test_forecast_82 = fitted_garch_82.forecast(horizon=test_size_days_82)
forecast_returns_82 = test_forecast_82.mean.iloc[-1].values / 100
# Tính predicted prices cho test set 8:2
predicted_prices_test_82 = []
current_price_82 = last_train_price_82
for i in range(test_size_days_82):
predicted_return = forecast_returns_82[i]
current_price_82 = current_price_82 * np.exp(predicted_return)
predicted_prices_test_82.append(current_price_82)
predicted_prices_test_82 = np.array(predicted_prices_test_82)
# Tính metrics cho 8:2
actual_test_prices_82 = test_data_82['Price'].values
mape_82 = mean_absolute_percentage_error(actual_test_prices_82, predicted_prices_test_82)
mse_82 = mean_squared_error(actual_test_prices_82, predicted_prices_test_82)
rmse_82 = np.sqrt(mse_82)
print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2:')
print(f'MAPE: {mape_82:.2f}%')
print(f'MSE: {mse_82:.2f}')
print(f'RMSE: {rmse_82:.2f}')
print(f'AIC: {aic_82:.2f}')
print(f'BIC: {bic_82:.2f}')
Kết quả đánh giá mô hình GARCH(1,1) - Split 8:2: MAPE: 0.40% MSE: 1.28 RMSE: 1.13 AIC: 16939.71 BIC: 16963.31
# Dự đoán 30, 60, 90 ngày tiếp theo cho 8:2
forecast_30_82, vol_30_82 = forecast_garch_prices(fitted_garch_82, last_price, 30)
forecast_60_82, vol_60_82 = forecast_garch_prices(fitted_garch_82, last_price, 60)
forecast_90_82, vol_90_82 = forecast_garch_prices(fitted_garch_82, last_price, 90)
# Tạo DataFrames cho 8:2
forecast_df_30_82 = pd.DataFrame(forecast_30_82, index=forecast_dates_30, columns=['Price'])
forecast_df_60_82 = pd.DataFrame(forecast_60_82, index=forecast_dates_60, columns=['Price'])
forecast_df_90_82 = pd.DataFrame(forecast_90_82, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa 8:2
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_82.index, predicted_prices_test_82, label='Dự đoán trên test set (8:2)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_82.index, forecast_df_30_82['Price'],
label='Dự đoán 30 ngày (8:2)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_82.index, forecast_df_60_82['Price'],
label='Dự đoán 60 ngày (8:2)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_82.index, forecast_df_90_82['Price'],
label='Dự đoán 90 ngày (8:2)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá XRP bằng GARCH(1,1) - Split 8:2', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f'\nDự đoán giá XRP 30 ngày tiếp theo (GARCH 8:2):')
print(f'Giá cao nhất: ${forecast_30_82.max():.4f}')
print(f'Giá thấp nhất: ${forecast_30_82.min():.4f}')
print(f'Giá trung bình: ${forecast_30_82.mean():.4f}')
print(f'Volatility trung bình: {vol_30_82.mean():.2f}%')
Dự đoán giá XRP 30 ngày tiếp theo (GARCH 8:2): Giá cao nhất: $2.7553 Giá thấp nhất: $1.1705 Giá trung bình: $1.9621 Volatility trung bình: 7.51%
Chia 9:1¶
Chuẩn bị dữ liệu 9:1¶
# Sử dụng dữ liệu đã chuẩn bị ở trên (data_clean với Log_Return)
print(f"Sử dụng dữ liệu đã chuẩn bị: {len(data_clean)} dòng")
Sử dụng dữ liệu đã chuẩn bị: 3368 dòng
# Chia dữ liệu train/test theo tỷ lệ 9:1
train_size_91 = int(len(data_clean) * 0.9)
train_data_91 = data_clean.iloc[0:train_size_91].copy()
test_data_91 = data_clean.iloc[train_size_91:].copy()
# Lấy returns cho train và test
train_returns_91 = train_data_91['Log_Return'].values
test_returns_91 = test_data_91['Log_Return'].values
print(f"Kích thước tập train 9:1: {len(train_data_91)}")
print(f"Kích thước tập test 9:1: {len(test_data_91)}")
Kích thước tập train 9:1: 3031 Kích thước tập test 9:1: 337
Huấn luyện mô hình GARCH 9:1¶
# Fit mô hình GARCH(1,1) cho split 9:1
print("Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...")
fitted_garch_91, garch_model_91 = build_garch_model(train_returns_91, p=1, q=1)
# Đánh giá mô hình
aic_91, bic_91 = evaluate_garch_model(fitted_garch_91)
Đang huấn luyện mô hình GARCH(1,1) cho split 9:1...
==================================================
THÔNG TIN MÔ HÌNH GARCH
==================================================
Constant Mean - GARCH Model Results
==============================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -9339.78
Distribution: Normal AIC: 18687.6
Method: Maximum Likelihood BIC: 18711.6
No. Observations: 3031
Date: Tue, Jun 03 2025 Df Residuals: 3030
Time: 20:39:19 Df Model: 1
Mean Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
mu -0.1458 9.665e-02 -1.509 0.131 [ -0.335,4.361e-02]
Volatility Model
========================================================================
coef std err t P>|t| 95.0% Conf. Int.
------------------------------------------------------------------------
omega 4.8806 1.920 2.541 1.104e-02 [ 1.117, 8.645]
alpha[1] 0.3566 0.120 2.975 2.931e-03 [ 0.122, 0.591]
beta[1] 0.5869 9.155e-02 6.411 1.450e-10 [ 0.407, 0.766]
========================================================================
Covariance estimator: robust
Ljung-Box test p-value (residuals): 0.0000
Ljung-Box test p-value (squared residuals): 0.0000
Đánh giá mô hình 9:1¶
# Dự đoán trên tập test 9:1
test_size_days_91 = len(test_data_91)
last_train_price_91 = train_data_91['Price'].iloc[-1]
# Forecast cho test period
test_forecast_91 = fitted_garch_91.forecast(horizon=test_size_days_91)
forecast_returns_91 = test_forecast_91.mean.iloc[-1].values / 100
# Tính predicted prices cho test set 9:1
predicted_prices_test_91 = []
current_price_91 = last_train_price_91
for i in range(test_size_days_91):
predicted_return = forecast_returns_91[i]
current_price_91 = current_price_91 * np.exp(predicted_return)
predicted_prices_test_91.append(current_price_91)
predicted_prices_test_91 = np.array(predicted_prices_test_91)
# Tính metrics cho 9:1
actual_test_prices_91 = test_data_91['Price'].values
mape_91 = mean_absolute_percentage_error(actual_test_prices_91, predicted_prices_test_91)
mse_91 = mean_squared_error(actual_test_prices_91, predicted_prices_test_91)
rmse_91 = np.sqrt(mse_91)
print(f'Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1:')
print(f'MAPE: {mape_91:.2f}%')
print(f'MSE: {mse_91:.2f}')
print(f'RMSE: {rmse_91:.2f}')
print(f'AIC: {aic_91:.2f}')
print(f'BIC: {bic_91:.2f}')
Kết quả đánh giá mô hình GARCH(1,1) - Split 9:1: MAPE: 0.59% MSE: 2.42 RMSE: 1.56 AIC: 18687.55 BIC: 18711.62
# Dự đoán 30, 60, 90 ngày tiếp theo cho 9:1
forecast_30_91, vol_30_91 = forecast_garch_prices(fitted_garch_91, last_price, 30)
forecast_60_91, vol_60_91 = forecast_garch_prices(fitted_garch_91, last_price, 60)
forecast_90_91, vol_90_91 = forecast_garch_prices(fitted_garch_91, last_price, 90)
# Tạo DataFrames cho 9:1
forecast_df_30_91 = pd.DataFrame(forecast_30_91, index=forecast_dates_30, columns=['Price'])
forecast_df_60_91 = pd.DataFrame(forecast_60_91, index=forecast_dates_60, columns=['Price'])
forecast_df_90_91 = pd.DataFrame(forecast_90_91, index=forecast_dates_90, columns=['Price'])
# Trực quan hóa 9:1
plt.figure(figsize=(16, 10))
plt.plot(data_clean.index, data_clean['Price'], label='Giá lịch sử', color='blue', linewidth=2, alpha=0.8)
plt.plot(test_data_91.index, predicted_prices_test_91, label='Dự đoán trên test set (9:1)', color='orange', linewidth=2, alpha=0.8)
plt.plot(forecast_df_30_91.index, forecast_df_30_91['Price'],
label='Dự đoán 30 ngày (9:1)', color='red', linestyle='--', linewidth=2, alpha=0.7)
plt.plot(forecast_df_60_91.index, forecast_df_60_91['Price'],
label='Dự đoán 60 ngày (9:1)', color='green', linestyle='--', linewidth=2, alpha=0.5)
plt.plot(forecast_df_90_91.index, forecast_df_90_91['Price'],
label='Dự đoán 90 ngày (9:1)', color='purple', linestyle='--', linewidth=2, alpha=0.3)
plt.axvline(x=data_clean.index[-1], color='black', linestyle=':', alpha=0.7,
label='Điểm bắt đầu dự đoán')
plt.title('Dự đoán giá XRP bằng GARCH(1,1) - Split 9:1', fontsize=16, fontweight='bold')
plt.xlabel('Ngày')
plt.ylabel('Giá (USD)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
print(f'\nDự đoán giá XRP 30 ngày tiếp theo (GARCH 9:1):')
print(f'Giá cao nhất: ${forecast_30_91.max():.4f}')
print(f'Giá thấp nhất: ${forecast_30_91.min():.4f}')
print(f'Giá trung bình: ${forecast_30_91.mean():.4f}')
print(f'Volatility trung bình: {vol_30_91.mean():.2f}%')
Dự đoán giá XRP 30 ngày tiếp theo (GARCH 9:1): Giá cao nhất: $2.6703 Giá thấp nhất: $1.1947 Giá trung bình: $1.9445 Volatility trung bình: 6.97%
So sánh 3 tỉ lệ GARCH¶
# So sánh chi tiết giữa 3 tỉ lệ chia dữ liệu cho GARCH
print("="*80)
print("SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH MODEL")
print("="*80)
# Thu thập thông tin từ 3 splits
garch_splits_info = {
'7:3': {
'train_size': len(train_data),
'test_size': len(test_data),
'mape': mape,
'mse': mse,
'rmse': rmse,
'aic': aic,
'bic': bic,
'avg_volatility': vol_30.mean()
},
'8:2': {
'train_size': len(train_data_82),
'test_size': len(test_data_82),
'mape': mape_82,
'mse': mse_82,
'rmse': rmse_82,
'aic': aic_82,
'bic': bic_82,
'avg_volatility': vol_30_82.mean()
},
'9:1': {
'train_size': len(train_data_91),
'test_size': len(test_data_91),
'mape': mape_91,
'mse': mse_91,
'rmse': rmse_91,
'aic': aic_91,
'bic': bic_91,
'avg_volatility': vol_30_91.mean()
}
}
# In bảng so sánh GARCH
for split, info in garch_splits_info.items():
print(f"\n{split} Split (GARCH):")
print(f" Kích thước train: {info['train_size']:,} mẫu")
print(f" Kích thước test: {info['test_size']:,} mẫu")
print(f" MAPE: {info['mape']:.2f}%")
print(f" MSE: {info['mse']:,.2f}")
print(f" RMSE: {info['rmse']:,.2f}")
print(f" AIC: {info['aic']:.2f}")
print(f" BIC: {info['bic']:.2f}")
print(f" Avg Volatility: {info['avg_volatility']:.2f}%")
================================================================================ SO SÁNH CHI TIẾT GIỮA 3 TỈ LỆ CHIA DỮ LIỆU - GARCH MODEL ================================================================================ 7:3 Split (GARCH): Kích thước train: 2,357 mẫu Kích thước test: 1,011 mẫu MAPE: 0.75% MSE: 1.19 RMSE: 1.09 AIC: 14872.56 BIC: 14895.62 Avg Volatility: 8.88% 8:2 Split (GARCH): Kích thước train: 2,694 mẫu Kích thước test: 674 mẫu MAPE: 0.40% MSE: 1.28 RMSE: 1.13 AIC: 16939.71 BIC: 16963.31 Avg Volatility: 7.51% 9:1 Split (GARCH): Kích thước train: 3,031 mẫu Kích thước test: 337 mẫu MAPE: 0.59% MSE: 2.42 RMSE: 1.56 AIC: 18687.55 BIC: 18711.62 Avg Volatility: 6.97%
# Vẽ biểu đồ so sánh các metrics cho GARCH
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
splits = ['7:3', '8:2', '9:1']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
# 1. So sánh MAPE
mape_values = [garch_splits_info[split]['mape'] for split in splits]
axes[0, 0].bar(splits, mape_values, color=colors, alpha=0.7)
axes[0, 0].set_title('So sánh MAPE (%) - GARCH', fontsize=14, fontweight='bold')
axes[0, 0].set_ylabel('MAPE (%)')
axes[0, 0].grid(True, alpha=0.3)
for i, v in enumerate(mape_values):
axes[0, 0].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 2. So sánh RMSE
rmse_values = [garch_splits_info[split]['rmse'] for split in splits]
axes[0, 1].bar(splits, rmse_values, color=colors, alpha=0.7)
axes[0, 1].set_title('So sánh RMSE (USD) - GARCH', fontsize=14, fontweight='bold')
axes[0, 1].set_ylabel('RMSE (USD)')
axes[0, 1].grid(True, alpha=0.3)
for i, v in enumerate(rmse_values):
axes[0, 1].text(i, v + 0.001, f'{v:.3f}', ha='center', va='bottom', fontweight='bold')
# 3. So sánh AIC
aic_values = [garch_splits_info[split]['aic'] for split in splits]
axes[0, 2].bar(splits, aic_values, color=colors, alpha=0.7)
axes[0, 2].set_title('So sánh AIC - GARCH', fontsize=14, fontweight='bold')
axes[0, 2].set_ylabel('AIC')
axes[0, 2].grid(True, alpha=0.3)
for i, v in enumerate(aic_values):
axes[0, 2].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')
# 4. So sánh BIC
bic_values = [garch_splits_info[split]['bic'] for split in splits]
axes[1, 0].bar(splits, bic_values, color=colors, alpha=0.7)
axes[1, 0].set_title('So sánh BIC - GARCH', fontsize=14, fontweight='bold')
axes[1, 0].set_ylabel('BIC')
axes[1, 0].grid(True, alpha=0.3)
for i, v in enumerate(bic_values):
axes[1, 0].text(i, v + 5, f'{v:.0f}', ha='center', va='bottom', fontweight='bold')
# 5. So sánh Average Volatility
vol_values = [garch_splits_info[split]['avg_volatility'] for split in splits]
axes[1, 1].bar(splits, vol_values, color=colors, alpha=0.7)
axes[1, 1].set_title('So sánh Avg Volatility (%) - GARCH', fontsize=14, fontweight='bold')
axes[1, 1].set_ylabel('Volatility (%)')
axes[1, 1].grid(True, alpha=0.3)
for i, v in enumerate(vol_values):
axes[1, 1].text(i, v + 0.1, f'{v:.2f}%', ha='center', va='bottom', fontweight='bold')
# 6. So sánh kích thước test set
test_sizes = [garch_splits_info[split]['test_size'] for split in splits]
axes[1, 2].bar(splits, test_sizes, color=colors, alpha=0.7)
axes[1, 2].set_title('So sánh Kích thước Test Set - GARCH', fontsize=14, fontweight='bold')
axes[1, 2].set_ylabel('Số mẫu test')
axes[1, 2].grid(True, alpha=0.3)
for i, v in enumerate(test_sizes):
axes[1, 2].text(i, v + 20, f'{v:,}', ha='center', va='bottom', fontweight='bold')
plt.tight_layout()
plt.show()
# Tạo DataFrame tổng hợp kết quả GARCH
garch_comparison_df = pd.DataFrame({
'Split': ['7:3', '8:2', '9:1'],
'Train_Size': [garch_splits_info[split]['train_size'] for split in splits],
'Test_Size': [garch_splits_info[split]['test_size'] for split in splits],
'MAPE (%)': [garch_splits_info[split]['mape'] for split in splits],
'RMSE (USD)': [garch_splits_info[split]['rmse'] for split in splits],
'MSE': [garch_splits_info[split]['mse'] for split in splits],
'AIC': [garch_splits_info[split]['aic'] for split in splits],
'BIC': [garch_splits_info[split]['bic'] for split in splits],
'Avg_Volatility (%)': [garch_splits_info[split]['avg_volatility'] for split in splits]
})
print("\nBẢNG TỔNG HỢP KẾT QUẢ GARCH:")
print("="*100)
print(garch_comparison_df.to_string(index=False, float_format='%.4f'))
BẢNG TỔNG HỢP KẾT QUẢ GARCH: ==================================================================================================== Split Train_Size Test_Size MAPE (%) RMSE (USD) MSE AIC BIC Avg_Volatility (%) 7:3 2357 1011 0.7534 1.0917 1.1919 14872.5642 14895.6248 8.8841 8:2 2694 674 0.3987 1.1327 1.2829 16939.7142 16963.3093 7.5125 9:1 3031 337 0.5941 1.5554 2.4191 18687.5537 18711.6203 6.9669
# Phân tích và đưa ra khuyến nghị cho GARCH
print("\n" + "="*80)
print("PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH MODEL")
print("="*80)
# Tìm split tốt nhất cho từng metric
best_mape_split_garch = splits[np.argmin([garch_splits_info[split]['mape'] for split in splits])]
best_rmse_split_garch = splits[np.argmin([garch_splits_info[split]['rmse'] for split in splits])]
best_aic_split_garch = splits[np.argmin([garch_splits_info[split]['aic'] for split in splits])]
best_bic_split_garch = splits[np.argmin([garch_splits_info[split]['bic'] for split in splits])]
print(f"\n1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH):")
print(f" • Tốt nhất theo MAPE: {best_mape_split_garch} ({garch_splits_info[best_mape_split_garch]['mape']:.2f}%)")
print(f" • Tốt nhất theo RMSE: {best_rmse_split_garch} ({garch_splits_info[best_rmse_split_garch]['rmse']:.3f} USD)")
print(f" • Tốt nhất theo AIC: {best_aic_split_garch} ({garch_splits_info[best_aic_split_garch]['aic']:.2f})")
print(f" • Tốt nhất theo BIC: {best_bic_split_garch} ({garch_splits_info[best_bic_split_garch]['bic']:.2f})")
# Tính điểm tổng hợp cho GARCH
def calculate_garch_rank_score(splits_info):
scores = {}
splits_list = list(splits_info.keys())
# Rank cho các metrics (thấp hơn = tốt hơn)
mape_rank = sorted(splits_list, key=lambda x: splits_info[x]['mape'])
rmse_rank = sorted(splits_list, key=lambda x: splits_info[x]['rmse'])
aic_rank = sorted(splits_list, key=lambda x: splits_info[x]['aic'])
bic_rank = sorted(splits_list, key=lambda x: splits_info[x]['bic'])
for split in splits_list:
# Điểm rank với trọng số cho GARCH
score = (mape_rank.index(split) + 1) * 0.35 + \
(rmse_rank.index(split) + 1) * 0.35 + \
(aic_rank.index(split) + 1) * 0.15 + \
(bic_rank.index(split) + 1) * 0.15
scores[split] = score
return scores
# Tính điểm tổng hợp
garch_scores = calculate_garch_rank_score(garch_splits_info)
best_overall_split_garch = min(garch_scores, key=garch_scores.get)
print(f"\n2. ĐIỂM TỔNG HỢP GARCH (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):")
for split in garch_splits_info.keys():
print(f" • {split}: {garch_scores[split]:.2f} điểm")
print(f"\n3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH:")
print(f" 🏆 MÔ HÌNH GARCH TỐT NHẤT: Split {best_overall_split_garch}")
print(f" 📊 Lý do:")
print(f" - MAPE: {garch_splits_info[best_overall_split_garch]['mape']:.2f}%")
print(f" - RMSE: {garch_splits_info[best_overall_split_garch]['rmse']:.3f} USD")
print(f" - AIC: {garch_splits_info[best_overall_split_garch]['aic']:.2f}")
print(f" - BIC: {garch_splits_info[best_overall_split_garch]['bic']:.2f}")
print(f" - Avg Volatility: {garch_splits_info[best_overall_split_garch]['avg_volatility']:.2f}%")
print(f" - Tập test có {garch_splits_info[best_overall_split_garch]['test_size']:,} mẫu")
print(f"\n4. NHẬN XÉT VỀ GARCH MODEL:")
print(" • GARCH hiệu quả trong việc mô hình hóa volatility clustering")
print(" • Phù hợp với dữ liệu tài chính có tính heteroskedasticity")
print(" • Cung cấp forecast về volatility cùng với price prediction")
print(" • AIC và BIC giúp đánh giá model fit và complexity")
print(f"\n ⚠️ LƯU Ý GARCH: Model này tập trung vào volatility modeling")
print(f" và phù hợp cho short-term forecasting với dữ liệu có tính volatility cao như XRP.")
================================================================================
PHÂN TÍCH VÀ KHUYẾN NGHỊ - GARCH MODEL
================================================================================
1. PHÂN TÍCH THEO TỪNG TIÊU CHÍ (GARCH):
• Tốt nhất theo MAPE: 8:2 (0.40%)
• Tốt nhất theo RMSE: 7:3 (1.092 USD)
• Tốt nhất theo AIC: 7:3 (14872.56)
• Tốt nhất theo BIC: 7:3 (14895.62)
2. ĐIỂM TỔNG HỢP GARCH (trọng số: MAPE=35%, RMSE=35%, AIC=15%, BIC=15%):
• 7:3: 1.70 điểm
• 8:2: 1.65 điểm
• 9:1: 2.65 điểm
3. KẾT LUẬN VÀ KHUYẾN NGHỊ GARCH:
🏆 MÔ HÌNH GARCH TỐT NHẤT: Split 8:2
📊 Lý do:
- MAPE: 0.40%
- RMSE: 1.133 USD
- AIC: 16939.71
- BIC: 16963.31
- Avg Volatility: 7.51%
- Tập test có 674 mẫu
4. NHẬN XÉT VỀ GARCH MODEL:
• GARCH hiệu quả trong việc mô hình hóa volatility clustering
• Phù hợp với dữ liệu tài chính có tính heteroskedasticity
• Cung cấp forecast về volatility cùng với price prediction
• AIC và BIC giúp đánh giá model fit và complexity
⚠️ LƯU Ý GARCH: Model này tập trung vào volatility modeling
và phù hợp cho short-term forecasting với dữ liệu có tính volatility cao như XRP.
# Vẽ so sánh volatility patterns giữa các splits
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
# Plot conditional volatility cho từng split
fitted_models = [fitted_garch, fitted_garch_82, fitted_garch_91]
train_data_list = [train_data, train_data_82, train_data_91]
split_names = ['7:3', '8:2', '9:1']
for i, (model, train_data_split, split_name) in enumerate(zip(fitted_models, train_data_list, split_names)):
axes[i].plot(train_data_split.index, train_data_split['Log_Return'] * 100,
alpha=0.7, label='Returns (%)', color='lightblue')
axes[i].plot(train_data_split.index, model.conditional_volatility,
color='red', linewidth=2, label='Conditional Volatility')
axes[i].set_title(f'Volatility Pattern - Split {split_name}', fontweight='bold')
axes[i].set_ylabel('Returns (%) / Volatility')
axes[i].legend()
axes[i].grid(True, alpha=0.3)
axes[i].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
# Vẽ biểu đồ radar cho so sánh tổng thể GARCH
def create_garch_radar_chart():
# Chuẩn hóa các metrics về scale 0-1 (1 là tốt nhất)
metrics = ['MAPE', 'RMSE', 'AIC', 'BIC', 'Test_Size']
# Lấy giá trị của từng metric (đảo ngược để 1 là tốt nhất)
data = {}
for split in splits:
mape_norm = 1 - (garch_splits_info[split]['mape'] - min([garch_splits_info[s]['mape'] for s in splits])) / \
(max([garch_splits_info[s]['mape'] for s in splits]) - min([garch_splits_info[s]['mape'] for s in splits]))
rmse_norm = 1 - (garch_splits_info[split]['rmse'] - min([garch_splits_info[s]['rmse'] for s in splits])) / \
(max([garch_splits_info[s]['rmse'] for s in splits]) - min([garch_splits_info[s]['rmse'] for s in splits]))
aic_norm = 1 - (garch_splits_info[split]['aic'] - min([garch_splits_info[s]['aic'] for s in splits])) / \
(max([garch_splits_info[s]['aic'] for s in splits]) - min([garch_splits_info[s]['aic'] for s in splits]))
bic_norm = 1 - (garch_splits_info[split]['bic'] - min([garch_splits_info[s]['bic'] for s in splits])) / \
(max([garch_splits_info[s]['bic'] for s in splits]) - min([garch_splits_info[s]['bic'] for s in splits]))
test_size_norm = (garch_splits_info[split]['test_size'] - min([garch_splits_info[s]['test_size'] for s in splits])) / \
(max([garch_splits_info[s]['test_size'] for s in splits]) - min([garch_splits_info[s]['test_size'] for s in splits]))
data[split] = [mape_norm, rmse_norm, aic_norm, bic_norm, test_size_norm]
# Tạo radar chart
angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist()
angles += angles[:1] # Complete the circle
fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
colors = ['#1f77b4', '#ff7f0e', '#2ca02c']
for i, (split, values) in enumerate(data.items()):
values += values[:1] # Complete the circle
ax.plot(angles, values, 'o-', linewidth=2, label=f'Split {split}', color=colors[i])
ax.fill(angles, values, alpha=0.25, color=colors[i])
ax.set_xticks(angles[:-1])
ax.set_xticklabels(metrics)
ax.set_ylim(0, 1)
ax.set_title('So sánh tổng thể các Split GARCH (1 = Tốt nhất)', size=16, fontweight='bold', pad=20)
ax.legend(loc='upper right', bbox_to_anchor=(1.2, 1.0))
ax.grid(True)
plt.tight_layout()
plt.show()
create_garch_radar_chart()
Kết luận cuối cùng - GARCH Model¶
Dựa trên phân tích toàn diện các tiêu chí đánh giá, mô hình GARCH với split tỉ lệ dữ liệu tốt nhất đã được xác định.
Các yếu tố được xem xét cho GARCH:
- MAPE (Mean Absolute Percentage Error): Đo lường độ chính xác dự đoán giá
- RMSE (Root Mean Square Error): Đo lường sai số tuyệt đối
- AIC (Akaike Information Criterion): Đánh giá model fit và complexity
- BIC (Bayesian Information Criterion): Đánh giá model với penalty cho complexity
- Volatility Forecasting: Khả năng dự đoán volatility (điểm mạnh của GARCH)
- Kích thước tập test: Đảm bảo độ tin cậy trong đánh giá
Ưu điểm của GARCH:
- Mô hình hóa hiệu quả volatility clustering trong dữ liệu tài chính
- Cung cấp forecast cho cả price và volatility
- Phù hợp với tính chất heteroskedasticity của XRP
- Cho phép risk assessment thông qua volatility predictions
Khuyến nghị sử dụng: Mô hình GARCH với tỉ lệ chia dữ liệu được đánh giá cao nhất sẽ được sử dụng cho các dự đoán cuối cùng về giá và volatility XRP, đặc biệt hiệu quả cho short-term forecasting và risk management.